1 /*
2  *   Magicard card printer family CUPS backend -- libusb-1.0 version
3  *
4  *   (c) 2017-2018 Solomon Peachy <pizza@shaftnet.org>
5  *
6  *   The latest version of this program can be found at:
7  *
8  *     http://git.shaftnet.org/cgit/selphy_print.git
9  *
10  *   This program is free software; you can redistribute it and/or modify it
11  *   under the terms of the GNU General Public License as published by the Free
12  *   Software Foundation; either version 2 of the License, or (at your option)
13  *   any later version.
14  *
15  *   This program is distributed in the hope that it will be useful, but
16  *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17  *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18  *   for more details.
19  *
20  *   You should have received a copy of the GNU General Public License
21  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
22  *
23  *          [http://www.gnu.org/licenses/gpl-2.0.html]
24  *
25  *   SPDX-License-Identifier: GPL-2.0+
26  *
27  */
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <time.h>
34 
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <string.h>
40 
41 #define BACKEND magicard_backend
42 
43 #include "backend_common.h"
44 
45 /* Exported */
46 #define USB_VID_MAGICARD     0x0C1F
47 #define USB_PID_MAGICARD_TANGO2E 0x1800
48 #define USB_PID_MAGICARD_ENDURO  0x4800   // ??
49 #define USB_PID_MAGICARD_ENDUROPLUS 0x880A // ??
50 
51 /* Gamma tables computed with this perl program:
52 
53   my $input_bpp = 8;
54   my $output_bpp = 6;
55   my $gamma = 1/1.8;  # or 1/2.2 or whatever.
56 
57   my $i;
58 
59   for (my $i = 0 ; $i < (2 ** $input_bpp) ; $i++) {
60     my $linear = $i / (2 ** $input_bpp);
61     my $gc = ($linear ** $gamma) * (2 ** $output_bpp);
62     $gc = int($gc);
63     print "$gc, ";
64   }
65 
66 */
67 
68 static uint8_t gammas[2][256] = {
69 	/* Gamma = 2.2 */
70 	{
71 		 0,  5,  7,  8,  9, 10, 11, 12, 13, 13, 14, 15, 15, 16, 17,
72 		17, 18, 18, 19, 19, 20, 20, 20, 21, 21, 22, 22, 23, 23, 23,
73 		24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28,
74 		29, 29, 29, 29, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32,
75 		33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 35, 36, 36,
76 		36, 36, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39,
77 		39, 39, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42,
78 		42, 42, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 45, 45,
79 		45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47,
80 		47, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 50,
81 		50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 52, 52,
82 		52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54,
83 		54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56,
84 		56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58,
85 		58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60,
86 		60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 61, 61, 62,
87 		62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63,
88 	},
89 	/* Gamma = 1.8 */
90 	{
91 		  0,  2,  4,  5,  6,  7,  7,  8,  9,  9, 10, 11, 11, 12, 12,
92 		 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19,
93 		 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24,
94 		 24, 24, 24, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28,
95 		 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32,
96 		 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 34, 35, 35, 35,
97 		 35, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 38, 38, 38, 38,
98 		 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41,
99 		 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44,
100 		 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 47, 47, 47,
101 		 47, 47, 47, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49,
102 		 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 52, 52, 52,
103 		 52, 52, 52, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54,
104 		 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 57, 57,
105 		 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 58, 59, 59, 59,
106 		 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61,
107 		 61, 61, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63,
108 	}
109 };
110 
111 struct magicard_printjob {
112 	uint8_t *databuf;
113 	int datalen;
114 
115 	int hdr_len;
116 	int copies;
117 };
118 
119 /* Private data structure */
120 struct magicard_ctx {
121 	struct libusb_device_handle *dev;
122 	uint8_t endp_up;
123 	uint8_t endp_down;
124 	int type;
125 
126 	struct marker marker;
127 };
128 
129 struct magicard_cmd_header {
130 	uint8_t guard[9];  /* 0x05 */
131 	uint8_t guard2[1]; /* 0x01 */
132 	uint8_t cmd[4]; /* 'REQ,' */
133 	uint8_t subcmd[4]; /* '???,' */
134 	uint8_t arg[4]; /* '???,' */
135 	uint8_t footer[2]; /* 0x1c 0x03 */
136 };
137 
138 struct magicard_cmd_simple_header {
139 	uint8_t guard[9];  /* 0x05 */
140 	uint8_t guard2[1]; /* 0x01 */
141 	uint8_t cmd[]; /* '???' */
142 //	uint8_t footer[2]; /* 0x1c 0x03 */
143 };
144 
145 struct magicard_resp_header {
146 	uint8_t guard[1]; /* 0x01 */
147 	uint8_t subcmd_arg[7]; /* '???,???' */
148 	uint8_t data[0]; /* freeform resp */
149 //	uint8_t term[2]; /* 0x2c 0x03 terminates! */
150 };
151 
152 struct magicard_requests {
153 	char *key;
154 	char *desc;
155 	uint8_t type;
156 };
157 
158 enum {
159 	TYPE_UNKNOWN = 0,
160 	TYPE_STRING,
161 	TYPE_STRINGINT,
162 	TYPE_STRINGINT_HEX,
163 	TYPE_IPADDR,
164 	TYPE_YESNO,
165 	TYPE_MODEL,
166 };
167 
168 /* Data definitions */
169 static struct magicard_requests magicard_sta_requests[] = {
170 	{ "MSR", "Printer Serial Number", TYPE_STRING },
171 	{ "PSR", "Print Head Serial Number", TYPE_STRING },
172 	{ "BSR", "PCB Serial Number", TYPE_STRING },
173 	{ "VRS", "Firmware Version", TYPE_STRING },
174 	{ "FDC", "Head Density", TYPE_STRINGINT },  /* 25 per step */
175 	{ "FSP", "Image Start", TYPE_STRINGINT }, /* 8 steps per pixel */
176 	{ "FEP", "Image End", TYPE_STRINGINT },   /* 8 steps per pixel */
177 	{ "FSS", "Ramp Adjust", TYPE_STRINGINT },
178 	{ "FPP", "Head Position", TYPE_STRINGINT }, /* L-R alignment */
179 	{ "MDL", "Model", TYPE_MODEL },  /* 0 == Standard.  Others? */
180 	{ "PID", "USB PID", TYPE_STRINGINT_HEX }, /* ASCII integer, but needs to be shown as hex */
181 	{ "VID", "USB VID", TYPE_STRINGINT_HEX }, /* ASCII integer, but needs to be shown as hex */
182 	{ "USN", "USB Serial Number", TYPE_STRING },
183 	{ "UPN", "USB Manufacturer", TYPE_STRING },
184 	{ "MAC", "Ethernet MAC Address", TYPE_STRING },
185 	{ "DYN", "Dynamic Address", TYPE_YESNO }, /* 1 == yes, 0 == no */
186 	{ "IPA", "IP Address", TYPE_IPADDR },  /* ASCII signed integer */
187 	{ "SNM", "IP Netmask", TYPE_IPADDR },  /* ASCII signed integer */
188 	{ "GWY", "IP Gateway", TYPE_IPADDR },  /* ASCII signed integer */
189 
190 	{ "TCQ", "Total Cards Printed", TYPE_STRINGINT },
191 	{ "TCP", "Prints on Head", TYPE_STRINGINT },
192 	{ "TCN", "Cleaning Cycles", TYPE_STRINGINT },
193 	{ "CCQ", "Cards Since Last Cleaning", TYPE_STRINGINT },
194 	{ "TPQ", "Total Panels Printed", TYPE_STRINGINT },
195 	{ "CCP", "Cards between Cleaning Prompts", TYPE_STRINGINT },
196 	{ "CPQ", "Panels Since Last Cleaning", TYPE_STRINGINT },
197 	{ "DFR", "Panels Remaining", TYPE_STRINGINT },  // cook somehow?
198 	{ "CLP", "Cleaning Prompt", TYPE_STRING },
199 
200 	// CRQ:  OFF  ??  Cleaning overdue?
201 	// CHK:  checksum of fw?  (8 chars, hex?)
202 	// TES:  ??? signed int?  IP addr?
203 	// RAMP:  ??? hangs.
204 
205 	{ NULL, NULL, 0 }
206 };
207 
208 // Sensors: CAM1 CAM2 TACHO FLIP DYE BARCODE LID FRONT REAR BUTTON TEMP ON OFF
209 // Languages: ENG ITA POR FRA DEU ESP SCH
210 
211 /* Helper functions */
magicard_build_cmd(uint8_t * buf,char * cmd,char * subcmd,char * arg)212 static int magicard_build_cmd(uint8_t *buf,
213 			       char *cmd, char *subcmd, char *arg)
214 {
215 	struct magicard_cmd_header *hdr = (struct magicard_cmd_header *) buf;
216 
217 	memset(hdr->guard, 0x05, sizeof(hdr->guard));
218 	hdr->guard2[0] = 0x01;
219 	memcpy(hdr->cmd, cmd, 3);
220 	hdr->cmd[3] = ',';
221 	memcpy(hdr->subcmd, subcmd, 3);
222 	hdr->subcmd[3] = ',';
223 	memcpy(hdr->arg, arg, 3);
224 	hdr->arg[3] = ',';
225 	hdr->footer[0] = 0x1c;
226 	hdr->footer[1] = 0x03;
227 
228 	return sizeof(*hdr);
229 }
230 
magicard_build_cmd_simple(uint8_t * buf,char * cmd)231 static int magicard_build_cmd_simple(uint8_t *buf,
232 				     char *cmd)
233 {
234 	struct magicard_cmd_simple_header *hdr = (struct magicard_cmd_simple_header *) buf;
235 	int len = strlen(cmd);
236 
237 	memset(hdr->guard, 0x05, sizeof(hdr->guard));
238 	hdr->guard2[0] = 0x01;
239 	strncpy((char*)hdr->cmd, cmd, len);
240 	hdr->cmd[len] = 0x1c;
241 	hdr->cmd[len+1] = 0x03;
242 
243 	return (sizeof(*hdr) + len + 2);
244 }
245 
246 
magicard_parse_resp(uint8_t * buf,uint16_t len,uint16_t * resplen)247 static uint8_t * magicard_parse_resp(uint8_t *buf, uint16_t len, uint16_t *resplen)
248 {
249 	struct magicard_resp_header *hdr = (struct magicard_resp_header *) buf;
250 
251 	*resplen = len - sizeof(hdr->guard) - sizeof(hdr->subcmd_arg) - 2;
252 
253 	return hdr->data;
254 }
255 
magicard_query_sensors(struct magicard_ctx * ctx)256 static int magicard_query_sensors(struct magicard_ctx *ctx)
257 {
258 	int ret = 0;
259 	int i;
260 	uint8_t buf[256];
261 	char buf2[24];
262 
263 	for (i = 1 ; ; i++) {
264 		int num = 0;
265 
266 		snprintf(buf2, sizeof(buf2), "SNR%d", i);
267 		ret = magicard_build_cmd_simple(buf, buf2);
268 
269 		if ((ret = send_data(ctx->dev, ctx->endp_down,
270 				     buf, ret)))
271 			return ret;
272 
273 		memset(buf, 0, sizeof(buf));
274 
275 		ret = read_data(ctx->dev, ctx->endp_up,
276 				buf, sizeof(buf), &num);
277 
278 		if (ret < 0)
279 			return ret;
280 
281 		if (!memcmp(buf, "END", 3))
282 			break;
283 
284 		buf[num] = 0;
285 		INFO("%s\n", buf);
286 	}
287 	return 0;
288 }
289 
magicard_selftest_card(struct magicard_ctx * ctx)290 static int magicard_selftest_card(struct magicard_ctx *ctx)
291 {
292 	int ret = 0;
293 	uint8_t buf[256];
294 
295 	ret = magicard_build_cmd_simple(buf, "TST,");
296 
297 	ret = send_data(ctx->dev, ctx->endp_down,
298 			buf, ret);
299 	return ret;
300 }
301 
magicard_reset(struct magicard_ctx * ctx)302 static int magicard_reset(struct magicard_ctx *ctx)
303 {
304 	int ret = 0;
305 	uint8_t buf[256];
306 
307 	ret = magicard_build_cmd_simple(buf, "RST,");
308 
309 	ret = send_data(ctx->dev, ctx->endp_down,
310 			buf, ret);
311 	return ret;
312 }
313 
magicard_eject(struct magicard_ctx * ctx)314 static int magicard_eject(struct magicard_ctx *ctx)
315 {
316 	int ret = 0;
317 	uint8_t buf[256];
318 
319 	ret = magicard_build_cmd_simple(buf, "EJT,");
320 
321 	ret = send_data(ctx->dev, ctx->endp_down,
322 			buf, ret);
323 	return ret;
324 }
325 
magicard_query_printer(struct magicard_ctx * ctx)326 static int magicard_query_printer(struct magicard_ctx *ctx)
327 {
328 	int ret = 0;
329 	int i;
330 	uint8_t buf[256];
331 	char buf2[24];
332 
333 	for (i = 1 ; ; i++) {
334 		int num = 0;
335 
336 		snprintf(buf2, sizeof(buf2), "QPR%d", i);
337 		ret = magicard_build_cmd_simple(buf, buf2);
338 
339 		if ((ret = send_data(ctx->dev, ctx->endp_down,
340 				     buf, ret)))
341 			return ret;
342 
343 		memset(buf, 0, sizeof(buf));
344 
345 		ret = read_data(ctx->dev, ctx->endp_up,
346 				buf, sizeof(buf), &num);
347 
348 		if (ret < 0)
349 			return ret;
350 
351 		if (!memcmp(buf, "END", 3))
352 			break;
353 
354 		buf[num] = 0;
355 		INFO("%s\n", buf);
356 	}
357 	return 0;
358 }
359 
magicard_query_status(struct magicard_ctx * ctx)360 static int magicard_query_status(struct magicard_ctx *ctx)
361 {
362 	int ret = 0;
363 	int i;
364 	uint8_t buf[256];
365 
366 	for (i = 0 ; ; i++) {
367 		uint16_t resplen = 0;
368 		uint8_t *resp;
369 		int num = 0;
370 
371 		if (magicard_sta_requests[i].key == NULL)
372 			break;
373 
374 		ret = magicard_build_cmd(buf, "REQ", "STA",
375 				   magicard_sta_requests[i].key);
376 
377 		if ((ret = send_data(ctx->dev, ctx->endp_down,
378 				     buf, ret)))
379 			return ret;
380 
381 		memset(buf, 0, sizeof(buf));
382 
383 		ret = read_data(ctx->dev, ctx->endp_up,
384 				buf, sizeof(buf), &num);
385 
386 		if (ret < 0)
387 			return ret;
388 
389 		resp = magicard_parse_resp(buf, num, &resplen);
390 		resp[resplen] = 0;
391 		switch(magicard_sta_requests[i].type) {
392 		case TYPE_IPADDR: {
393 			int32_t ipaddr;
394 			uint8_t *addr = (uint8_t *) &ipaddr;
395 			ipaddr = atoi((char*)resp);
396 			INFO("%s:\t%d.%d.%d.%d\n",
397 			     magicard_sta_requests[i].desc,
398 			     addr[3], addr[2], addr[1], addr[0]);
399 			     break;
400 		}
401 		case TYPE_YESNO: {
402 			int val = atoi((char*)resp);
403 			INFO("%s:\t%s\n",
404 			     magicard_sta_requests[i].desc,
405 			     val? "Yes" : "No");
406 			break;
407 		}
408 		case TYPE_MODEL: {
409 			int val = atoi((char*)resp);
410 			INFO("%s:\t%s\n",
411 			     magicard_sta_requests[i].desc,
412 			     val == 0? "Standard" : "Unknown");
413 			break;
414 		}
415 		case TYPE_STRINGINT_HEX: {
416 			int val = atoi((char*)resp);
417 			INFO("%s:\t%X\n",
418 			     magicard_sta_requests[i].desc,
419 			     val);
420 			break;
421 		}
422 		case TYPE_STRINGINT:
423 			// treat differently?
424 		case TYPE_STRING:
425 		case TYPE_UNKNOWN:
426 		default:
427 			INFO("%s:\t%s\n",
428 			     magicard_sta_requests[i].desc,
429 			     resp);
430 		}
431 	}
432 
433 	return ret;
434 }
435 
436 /* Main driver */
magicard_init(void)437 static void* magicard_init(void)
438 {
439 	struct magicard_ctx *ctx = malloc(sizeof(struct magicard_ctx));
440 	if (!ctx) {
441 		ERROR("Memory Allocation Failure!");
442 		return NULL;
443 	}
444 	memset(ctx, 0, sizeof(struct magicard_ctx));
445 	return ctx;
446 }
447 
magicard_attach(void * vctx,struct libusb_device_handle * dev,int type,uint8_t endp_up,uint8_t endp_down,uint8_t jobid)448 static int magicard_attach(void *vctx, struct libusb_device_handle *dev, int type,
449 			   uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
450 {
451 	struct magicard_ctx *ctx = vctx;
452 
453 	UNUSED(jobid);
454 
455 	ctx->dev = dev;
456 	ctx->endp_up = endp_up;
457 	ctx->endp_down = endp_down;
458 	ctx->type = type;
459 
460 	ctx->marker.color = "#00FFFF#FF00FF#FFFF00";  // XXX YMCK too!
461 	ctx->marker.name = "Unknown"; // LC1/LC3/LC6/LC8
462 	ctx->marker.levelmax = -1;
463 	ctx->marker.levelnow = -2;
464 
465 	return CUPS_BACKEND_OK;
466 }
467 
magicard_cleanup_job(const void * vjob)468 static void magicard_cleanup_job(const void *vjob)
469 {
470 	const struct magicard_printjob *job = vjob;
471 
472 	if (job->databuf)
473 		free(job->databuf);
474 
475 	free((void*)job);
476 }
477 
downscale_and_extract(int gamma,uint32_t pixels,uint8_t * y_i,uint8_t * m_i,uint8_t * c_i,uint8_t * y_o,uint8_t * m_o,uint8_t * c_o,uint8_t * k_o)478 static void downscale_and_extract(int gamma, uint32_t pixels,
479 				  uint8_t *y_i, uint8_t *m_i, uint8_t *c_i,
480 				  uint8_t *y_o, uint8_t *m_o, uint8_t *c_o, uint8_t *k_o)
481 {
482 	uint32_t i;
483 
484 	for (i = 0 ; i < pixels; i++)
485 	{
486 		uint8_t y, m, c;
487 		uint8_t k = 0;
488 		uint32_t j;
489 		uint32_t row;
490 		uint32_t col;
491 		uint32_t b_offset;
492 		uint8_t b_shift;
493 
494 		/* Downscale color planes from 8bpp -> 6bpp; */
495 		if (gamma) {
496 			if (gamma > 2)
497 				gamma = 2;
498 			gamma--;
499 			y = gammas[gamma][*y_i++];
500 			m = gammas[gamma][*m_i++];
501 			c = gammas[gamma][*c_i++];
502 		} else {
503 			y = *y_i++ >> 2;
504 			m = *m_i++ >> 2;
505 			c = *c_i++ >> 2;
506 		}
507 
508 		/* Extract "true black" from ymc data, if enabled */
509 		if (k_o && y == 0x3f && m == 0x3f && c == 0x3f) {
510 			k = 1;
511 			y = m = c = 0;
512 		}
513 
514 		/* Compute row number and offsets */
515 		row = i / 672;
516 		col = i - (row * 672);
517 		b_offset = col / 8;
518 		b_shift = 7 - (col - (b_offset * 8));
519 
520 		/* Now, for each row, break it down into sub-chunks */
521 		for (j = 0 ; j < 6 ; j++) {
522 			if (b_shift == 7) {
523 				y_o[row * 504 + j * 84 + b_offset] = 0;
524 				m_o[row * 504 + j * 84 + b_offset] = 0;
525 				c_o[row * 504 + j * 84 + b_offset] = 0;
526 			}
527 			if (y & (1 << j))
528 				y_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
529 			if (m & (1 << j))
530 				m_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
531 			if (c & (1 << j))
532 				c_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
533 		}
534 
535 		/* And resin black, if enabled */
536 		if (k_o) {
537 			if (b_shift == 7) {
538 				k_o[row * 84 + b_offset] = 0;
539 			}
540 			if (k)
541 				k_o[row * 84 + b_offset] |= (1 << b_shift);
542 		}
543 	}
544 }
545 
546 #define MAX_HEADERS_LEN 2048
547 #define MAX_PRINTJOB_LEN (1016*672*4) + MAX_HEADERS_LEN  /* 1016*672 * 4color */
548 #define INITIAL_BUF_LEN 1024
magicard_read_parse(void * vctx,const void ** vjob,int data_fd,int copies)549 static int magicard_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
550 	struct magicard_ctx *ctx = vctx;
551 	uint8_t initial_buf[INITIAL_BUF_LEN + 1];
552 	uint32_t buf_offset = 0;
553 	int i;
554 
555 	uint8_t *in_y, *in_m, *in_c;
556 	uint8_t *out_y, *out_m, *out_c, *out_k;
557 	uint32_t len_y = 0, len_m = 0, len_c = 0, len_k = 0;
558 	int gamma = 0;
559 
560 	uint8_t x_gp_8bpp;
561 	uint8_t x_gp_rk;
562 	uint8_t k_only;
563 
564 	struct magicard_printjob *job = NULL;
565 
566 	if (!ctx)
567 		return CUPS_BACKEND_FAILED;
568 
569 	job = malloc(sizeof(*job));
570 	if (!job) {
571 		ERROR("Memory allocation failure!\n");
572 		return CUPS_BACKEND_RETRY_CURRENT;
573 	}
574 	memset(job, 0, sizeof(*job));
575 	job->copies = copies;
576 
577 	/* Read in the first chunk */
578 	i = read(data_fd, initial_buf, INITIAL_BUF_LEN);
579 	if (i < 0) {
580 		magicard_cleanup_job(job);
581 		return i;
582 	} else if (i == 0) {
583 		magicard_cleanup_job(job);
584 		return CUPS_BACKEND_CANCEL;  /* Ie no data, we're done */
585 	} else if (i < INITIAL_BUF_LEN) {
586 		magicard_cleanup_job(job);
587 		return CUPS_BACKEND_CANCEL;
588 	}
589 
590 	/* Basic Sanity Check */
591 	if (initial_buf[0] != 0x05 ||
592 	    initial_buf[64] != 0x01 ||
593 	    initial_buf[65] != 0x2c) {
594 		ERROR("Unrecognized header data format @%d!\n", job->datalen);
595 		magicard_cleanup_job(job);
596 		return CUPS_BACKEND_CANCEL;
597 	}
598 
599 	initial_buf[INITIAL_BUF_LEN] = 0;
600 
601 	/* We can start allocating! */
602 	if (job->databuf) {
603 		free(job->databuf);
604 		job->databuf = NULL;
605 	}
606 	job->datalen = 0;
607 	job->databuf = malloc(MAX_PRINTJOB_LEN);
608 	if (!job->databuf) {
609 		ERROR("Memory allocation failure!\n");
610 		magicard_cleanup_job(job);
611 		return CUPS_BACKEND_RETRY_CURRENT;
612 	}
613 
614 	/* Copy over initial header */
615 	memcpy(job->databuf + job->datalen, initial_buf + buf_offset, 65);
616 	job->datalen += 65;
617 	buf_offset += 65;
618 
619 	/* Start parsing headers */
620 	x_gp_8bpp = x_gp_rk = k_only = job->hdr_len = 0;
621 
622 	char *ptr;
623 	ptr = strtok((char*)initial_buf + ++buf_offset, ",\x1c");
624 	while (ptr
625 	       && ((ptr - (char*)initial_buf) < INITIAL_BUF_LEN)
626 	       && ((ptr - (char*)initial_buf) + strnlen(ptr, INITIAL_BUF_LEN) < INITIAL_BUF_LEN)
627 	       && *ptr != 0x1c) {
628 		if (!strcmp("X-GP-8", ptr)) {
629 			x_gp_8bpp = 1;
630 		} else if (!strncmp("TDT", ptr, 3)) {
631 			/* Strip out the timestamp, replace it with one from the backend */
632 		} else if (!strncmp("IMF", ptr,3)) {
633 			/* Strip out the image format, replace it with backend */
634 //		} else if (!strncmp("ESS", ptr, 3)) {
635 //			/* Strip out copies */
636 		} else if (!strcmp("X-GP-RK", ptr)) {
637 			x_gp_rk = 1;
638 		} else if (!strncmp("ICC", ptr,3)) {
639 			/* Gamma curve is not handled by printer,
640 			   strip it out and use it! */
641 			gamma = atoi(ptr + 3);
642 		} else if (!strncmp("SZ", ptr, 2)) {
643 			if (ptr[2] == 'B') {
644 				len_y = atoi(ptr + 3);
645 			} else if (ptr[2] == 'G') {
646 				len_m = atoi(ptr + 3);
647 			} else if (ptr[2] == 'R') {
648 				len_c = atoi(ptr + 3);
649 			} else if (ptr[2] == 'K') {
650 				len_k = atoi(ptr + 3);
651 			}
652 		} else {
653 			/* Safety valve */
654 			if (strlen(ptr) + job->datalen > MAX_HEADERS_LEN) {
655 				ERROR("headers too long, bogus job!\n");
656 				magicard_cleanup_job(job);
657 				return CUPS_BACKEND_CANCEL;
658 			}
659 
660 			/* Everything else goes in */
661 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",%s", ptr);
662 		}
663 
664 		/* Keep going */
665 		buf_offset += strlen(ptr) + 1;
666 		/* Peek ahead to see if this is it */
667 		if (initial_buf[buf_offset + 1] == 0x1c)
668 			break;
669 		/* Otherwise continue to the next token */
670 		ptr = strtok(NULL, ",\x1c");
671 	}
672 
673 	/* Sanity checks */
674 	if (!len_y || !len_m || !len_c) {
675 		ERROR("Plane lengths missing? %u/%u/%u!\n", len_y, len_m, len_c);
676 		magicard_cleanup_job(job);
677 		return CUPS_BACKEND_CANCEL;
678 	}
679 	if (len_y != len_m || len_y != len_c) {
680 		ERROR("Inconsistent data plane lengths! %u/%u/%u!\n", len_y, len_m, len_c);
681 		magicard_cleanup_job(job);
682 		return CUPS_BACKEND_CANCEL;
683 	}
684 	if (x_gp_rk && len_k) {
685 		ERROR("Data stream already has a K layer!\n");
686 		magicard_cleanup_job(job);
687 		return CUPS_BACKEND_CANCEL;
688 	}
689 
690 	/* Generate a timestamp */
691 	job->datalen += sprintf((char*)job->databuf + job->datalen, ",TDT%08X", (uint32_t) time(NULL));
692 
693 	/* Generate image format tag */
694 	if (k_only == 1) {
695 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFK");
696 	} else if (x_gp_rk || len_k) {
697 		/* We're adding K, so make this BGRK */
698 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFBGRK");
699 	} else {
700 		/* Just BGR */
701 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFBGR");
702 	}
703 
704 	/* Insert SZB/G/R/K length descriptors */
705 	if (x_gp_8bpp) {
706 		if (k_only == 1) {
707 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_c / 8);
708 		} else {
709 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZB%u", len_y * 6 / 8);
710 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZG%u", len_m * 6 / 8);
711 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZR%u", len_c * 6 / 8);
712 			/* Add in a SZK length indication if requested */
713 			if (x_gp_rk == 1) {
714 				job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_c / 8);
715 			}
716 		}
717 	} else {
718 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZB%u", len_y);
719 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZG%u", len_m);
720 		job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZR%u", len_c);
721 		/* Add in a SZK length indication if requested */
722 		if (len_k) {
723 			job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_k);
724 		}
725 	}
726 
727 	/* Terminate command stream */
728 	job->databuf[job->datalen++] = 0x1c;
729 
730 	/* Let's figure out how long the image data stream is supposed to be. */
731 	uint32_t remain;
732 	if (k_only) {
733 		remain = len_k + 3;
734 	} else {
735 		remain = len_y + len_m + len_c + 3 * 3;
736 		if (len_k)
737 			remain += len_k + 3;
738 	}
739 	/* Offset the stuff we already read in. */
740 	remain -= INITIAL_BUF_LEN - buf_offset;
741 	remain++;  /* Add in a byte for the end of job marker. This is our final value. */
742 
743 	/* This is how much of the initial buffer is the header length. */
744 	job->hdr_len = job->datalen;
745 
746 	if (x_gp_8bpp) {
747 		uint32_t srcbuf_offset = INITIAL_BUF_LEN - buf_offset;
748 		uint8_t *srcbuf = malloc(MAX_PRINTJOB_LEN);
749 		if (!srcbuf) {
750 			magicard_cleanup_job(job);
751 			ERROR("Memory allocation failure!\n");
752 			return CUPS_BACKEND_RETRY_CURRENT;
753 		}
754 
755 		memcpy(srcbuf, initial_buf + buf_offset, srcbuf_offset);
756 
757 		/* Finish loading the data */
758 		while (remain > 0) {
759 			i = read(data_fd, srcbuf + srcbuf_offset, remain);
760 			if (i < 0) {
761 				ERROR("Data Read Error: %d (%u) @%u)\n", i, remain, srcbuf_offset);
762 				magicard_cleanup_job(job);
763 				free(srcbuf);
764 				return i;
765 			}
766 			if (i == 0) {
767 				ERROR("Short read! (%d/%u)\n", i, remain);
768 				magicard_cleanup_job(job);
769 				free(srcbuf);
770 				return CUPS_BACKEND_CANCEL;
771 			}
772 			srcbuf_offset += i;
773 			remain -= i;
774 		}
775 
776 		// XXX handle conversion of K-only jobs.  if needed.
777 
778 		/* set up source pointers */
779 		in_y = srcbuf;
780 		in_m = in_y + len_y + 3;
781 		in_c = in_m + len_m + 3;
782 
783 		/* Set up destination pointers */
784 		out_y = job->databuf + job->datalen;
785 		out_m = out_y + (len_y * 6 / 8) + 3;
786 		out_c = out_m + (len_m * 6 / 8) + 3;
787 		out_k = out_c + (len_c * 6 / 8) + 3;
788 
789 		/* Termination of each plane */
790 		memcpy(out_m - 3, in_y + len_y, 3);
791 		memcpy(out_c - 3, in_m + len_m, 3);
792 		memcpy(out_k - 3, in_c + len_c, 3);
793 
794 		if (!x_gp_rk)
795 			out_k = NULL;
796 
797 		INFO("Converting image data to printer's native format %s\n", x_gp_rk ? "and extracting K channel" : "");
798 
799 		downscale_and_extract(gamma, len_y, in_y, in_m, in_c,
800 				      out_y, out_m, out_c, out_k);
801 
802 		/* Pad out the length appropriately. */
803 		job->datalen += ((len_c * 6 / 8) + 3) * 3;
804 
805 		/* If there's a K plane, compute length.. */
806 		if (out_k) {
807 			job->datalen += (len_c / 8);
808 			job->databuf[job->datalen++] = 0x1c;
809 			job->databuf[job->datalen++] = 0x4b;
810 			job->databuf[job->datalen++] = 0x3a;
811 		}
812 
813 		/* Terminate the entire stream */
814 		job->databuf[job->datalen++] = 0x03;
815 
816 		free(srcbuf);
817 	} else {
818 		uint32_t srcbuf_offset = INITIAL_BUF_LEN - buf_offset;
819 		memcpy(job->databuf + job->datalen, initial_buf + buf_offset, srcbuf_offset);
820 		job->datalen += srcbuf_offset;
821 
822 		/* Finish loading the data */
823 		while (remain > 0) {
824 			i = read(data_fd, job->databuf + job->datalen, remain);
825 			if (i < 0) {
826 				ERROR("Data Read Error: %d (%u) @%d)\n", i, remain, job->datalen);
827 				magicard_cleanup_job(job);
828 				return i;
829 			}
830 			if (i == 0) {
831 				magicard_cleanup_job(job);
832 				ERROR("Short read! (%d/%u)\n", i, remain);
833 				return CUPS_BACKEND_CANCEL;
834 			}
835 			job->datalen += i;
836 			remain -= i;
837 		}
838 	}
839 
840 	*vjob = job;
841 
842 	return CUPS_BACKEND_OK;
843 }
844 
magicard_main_loop(void * vctx,const void * vjob)845 static int magicard_main_loop(void *vctx, const void *vjob) {
846 	struct magicard_ctx *ctx = vctx;
847 	int ret;
848 	int copies;
849 
850 	const struct magicard_printjob *job = vjob;
851 
852 	// XXX printer handles copy generation..
853 	// but it's a numeric parameter.  Bleh.
854 	if (!ctx)
855 		return CUPS_BACKEND_FAILED;
856 	if (!job)
857 		return CUPS_BACKEND_FAILED;
858 
859 	copies = job->copies;
860 top:
861 	if ((ret = send_data(ctx->dev, ctx->endp_down,
862 			     job->databuf, job->hdr_len)))
863 		return CUPS_BACKEND_FAILED;
864 
865 	if ((ret = send_data(ctx->dev, ctx->endp_down,
866 			     job->databuf + job->hdr_len, job->datalen - job->hdr_len)))
867 		return CUPS_BACKEND_FAILED;
868 
869 	/* Clean up */
870 	if (terminate)
871 		copies = 1;
872 
873 	INFO("Print complete (%d copies remaining)\n", copies - 1);
874 
875 	if (copies && --copies) {
876 		goto top;
877 	}
878 
879 	return CUPS_BACKEND_OK;
880 }
881 
magicard_cmdline(void)882 static void magicard_cmdline(void)
883 {
884 	DEBUG("\t\t[ -s ]           # Query status\n");
885 	DEBUG("\t\t[ -q ]           # Query information\n");
886 	DEBUG("\t\t[ -I ]           # Query printer sensors\n");
887 	DEBUG("\t\t[ -E ]           # Eject card\n");
888 	DEBUG("\t\t[ -T ]           # Print self-test card\n");
889 	DEBUG("\t\t[ -R ]           # Reset printer\n");
890 }
891 
magicard_cmdline_arg(void * vctx,int argc,char ** argv)892 static int magicard_cmdline_arg(void *vctx, int argc, char **argv)
893 {
894 	struct magicard_ctx *ctx = vctx;
895 	int i, j = 0;
896 
897 	if (!ctx)
898 		return -1;
899 
900 	while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "sqEIRT")) >= 0) {
901 		switch(i) {
902 		GETOPT_PROCESS_GLOBAL
903 		case 's':
904 			j = magicard_query_status(ctx);
905 			break;
906 		case 'q':
907 			j = magicard_query_printer(ctx);
908 			break;
909 		case 'E':
910 			j = magicard_eject(ctx);
911 			break;
912 		case 'I':
913 			j = magicard_query_sensors(ctx);
914 			break;
915 		case 'R':
916 			j = magicard_reset(ctx);
917 			break;
918 		case 'T':
919 			j = magicard_selftest_card(ctx);
920 			break;
921 		}
922 
923 		if (j) return j;
924 	}
925 
926 	return 0;
927 }
928 
magicard_query_markers(void * vctx,struct marker ** markers,int * count)929 static int magicard_query_markers(void *vctx, struct marker **markers, int *count)
930 {
931 	struct magicard_ctx *ctx = vctx;
932 
933 	*markers = &ctx->marker;
934 	*count = 1;
935 
936 	return CUPS_BACKEND_OK;
937 }
938 
939 static const char *magicard_prefixes[] = {
940 	"magicard", // Family name
941 	"magicard-tango-2e", "magicard-enduro", "magicard-enduroplus",
942 	// extras
943 	"magicard-rio-2e",
944 	// backwards compatibility
945 	"tango2e", "enduro", "enduroplus",
946 	NULL
947 };
948 
949 struct dyesub_backend magicard_backend = {
950 	.name = "Magicard family",
951 	.version = "0.16",
952 	.uri_prefixes = magicard_prefixes,
953 	.cmdline_arg = magicard_cmdline_arg,
954 	.cmdline_usage = magicard_cmdline,
955 	.init = magicard_init,
956 	.attach = magicard_attach,
957 	.cleanup_job = magicard_cleanup_job,
958 	.read_parse = magicard_read_parse,
959 	.main_loop = magicard_main_loop,
960 	.query_markers = magicard_query_markers,
961 	.devices = {
962 		{ USB_VID_MAGICARD, USB_PID_MAGICARD_TANGO2E, P_MAGICARD, NULL, "magicard-tango2e"},
963 		{ USB_VID_MAGICARD, USB_PID_MAGICARD_ENDURO, P_MAGICARD, NULL, "magicard-enduro"},
964 		{ USB_VID_MAGICARD, USB_PID_MAGICARD_ENDUROPLUS, P_MAGICARD, NULL, "magicard-enduroplus"},
965 		{ USB_VID_MAGICARD, 0xFFFF, P_MAGICARD, NULL, "magicard"},
966 		{ 0, 0, 0, NULL, "magicard"}
967 	}
968 };
969 
970 /* Magicard family Spool file format (Tango2e/Rio2e/AvalonE family)
971 
972   This one was rather fun to figure out.
973 
974   * Job starts with a sequence of 64 '0x05'
975   * Command sequence starts with 0x01
976   * Commands are textual and comma-separated.
977     * Most are passed through ignored, except for:
978       * SZB, SZG, SZR, SZK  -- indicate length of respective data plane
979       * IMF -- Image format (BGR/BGRK/K)
980       * X-GP-8 -- Tells backend to convert from Gutenprint's 8bpp data
981       * X-GP-RK -- Tells backend to extract K channel from color data
982   * Command sequence ends with 0x1c
983   * Image plane data follows, in the order of the SZ# entries
984     * Plane lengths are specified by the SZ# entry.
985     * Color planes are actually Y/M/C rather than B/G/R!
986     * Each plane terminates with 0x1c __ 0x3a, where __ is 0x42, 0x47, 0x52,
987       and 0x4b for B/G/R/K respectively.  Terminator is _not_ part of length.
988     * Image data is 6bpp for B/G/R and 1bpp for K, 672*1016 pixels
989       * Organized in a series of 84-byte rows.
990       * Byte data is LSB first.
991       * Each row is a single stripe of a single bit of a pixel, so
992         color data is b0b0b0b0.. b1b1b1b1.. .. b5b5b5b5.
993   * Job ends with 0x03
994 
995   ** ** ** ** ** **
996 
997   Firmware updates:
998 
999   0x05 (x9) 0x01 REQ,FRM###### 0x1c
1000 
1001   Where ###### is the length of the firmware image.
1002 
1003   Then send over 64 bytes at a time until it's done.
1004 
1005   Then send 0x03 to mark end of job.
1006 
1007   Follow it with:
1008 
1009   0x01 STA,CHK########, 0x03   (8-digit checksum?)
1010 
1011   0x05 (x9) 0x01 REQ,UPG, 0x1c 0x03
1012 
1013   ** ** ** ** ** **
1014 
1015   Known commands seen in print jobs:
1016 
1017   BAC%s    Backside format (CKO, KO, C, CO, K) -- Only used with Duplex.
1018   CKI%s    Custom Holokote (ON or OFF)
1019   CPW%s    Color power level (0-100, default 50)
1020   DPX%s    Duplex (ON or OFF)
1021   EOI%d    Card alignment end (0-100, default 50)
1022   ESS%d    Number of copies (1-?)
1023   HGT%d    Image Height (always seems to be 1016)
1024   HKM%06X  Holokote hole.  bitwise number, each bit corresponds to an area.
1025   HKT%d    Holokote type (1 is "ultra secure, 2 is "interlocking rings", etc)
1026   HPH%s    Holopatch (ON or OFF)
1027   IMF%s    Image Data Format (BGR, BGRK, K)
1028   KPW%s    Black power level (0-100, default 50)
1029   LAN%s    Printer display lanaguage (ENG, ITA, POR, FRA, DEU, ESP, SCH)
1030   LC%d     Force media type (LC1, LC3, LC6, LC8 for YMCKO/MONO/KO/YMCKOK)
1031   NCT%d,%d,%d,%d  Overcoat hole
1032   OPW%s    Overcoat power level (0-100, default 50)
1033   OVR%s    Overcoat (ON or OFF)
1034   PAG%d    Page number (always 1, except 2 if printing duplex backside)
1035   PAT%d    Holopatch area  (0-24)
1036   REJ%s    Reject faulty cards (ON or OFF)
1037   SOI%d    Card alignment start (0-100, default 50)
1038   SLW%s    Colorsure (ON or OFF)
1039   SZB%d    Blue data length
1040   SZG%d    Green data length
1041   SZK%d    Black data length
1042   SZR%d    Red data length
1043   TDT%08X  Driver-supplied timestamp of print job.
1044   USF%s    Holokote (ON or OFF)
1045   VER%s    Inform the printer of the driver version  (seems to be ignored)
1046   WID%d    Image Width (always seems to be 642)
1047 
1048     Mag-stripe encoding:
1049 
1050   MAG%d   Magstripe position (1, 2, or 3)
1051   BPI%d   Bits per Inch (75 or 210)
1052   MPC%d   Character encoding (5 or 7)
1053   COE%s   'H'igh or 'L'ow coercivity
1054 
1055     Unknown commands seen in print jobs:
1056 
1057   DDD%s    ? (only seen '50')  -- Could it be K alignment?
1058   KEE      ?
1059   NNN%s    ?  (Seen 'OFF')
1060   NOC%d    ?  (Seen '1')  (Seems to start a job)
1061   PCT%d,%d,%d,%d  ?  Print area, seems fixed @ 0,0, 1025, 641)
1062   RT2      ?
1063   TRO%d    ?  (Seen '0', appears with Holokote)
1064   XCO%d    ? X start offset (always seems to be 0)
1065   YCO%d    ? Y start offset (always seems to be 0)
1066 
1067     Unknown commands:  (Seen in firmware guts)
1068 
1069   AAA
1070   AMS
1071   BBB%d   Numeric parameter
1072   CLR
1073   FBF
1074   FTC
1075   HFD%s   String parameter
1076   IPM
1077   KKK
1078   LBL
1079   LLL
1080   LRC
1081   MGV%s  "ON" or "OFF"  but no idea
1082   MMM
1083   PAR
1084   RDM
1085   SNR
1086   SSP
1087 
1088    Unknown commands unique to Tango +L (ie w/ Laminator support)
1089 
1090   FRN
1091   LAM
1092   LAM_DLY
1093   LAM_SPD
1094   LAM_LEN
1095   LAM_END
1096   LAM_STA
1097   LAM_DEG
1098   LAM_FLM
1099   LAM_KBD
1100   LAM_MOD
1101 
1102     Commands consumed by backend:
1103 
1104   ICC%d    Gamma curve (0, 1, 2) -- off, 2.2, or 1.8 respectively.
1105   X-GP-8   Raw data is 8bpp. needs to be converted.
1106   X-GP-RK  Extract K channel from color data.
1107 
1108    Open questions:
1109 
1110    * How to query/read magstripe
1111    * How to set IP address (etc)
1112    * How to set other parameters
1113 
1114    "Simple Commands"  (REQ,....,)
1115 
1116   RST    Reset printer
1117   TST    Generate self-test page
1118   EJT     Eject card
1119 
1120    Other "Simple commands" referenced in Rio Pro/Enduro+ docs
1121 
1122   DEALERSERVICE%s ON/OFF (enter/exit dealer service mode)
1123   CAM     Reset print head cam position
1124   CHP%s   UP/DOWN  Feed  card into smart encoder
1125   CLN     Cleaning cycle
1126   DYE     Re-init dye film
1127   ENC     Test encoding cycle
1128   FEED%d  0/1,+  0/1, load card into standby, >1 feed N cards.
1129   FLIP    Flip card in printer
1130   FRN%s   ON/OFF -- Film saving
1131   HEAD%s  UP/DOWN -- Raise or lower print head.
1132   RAMP%d  0-100 Density ramp, 50 default
1133   SET     Saves settings into NVDATA
1134   STN     Re-init Holokote
1135   SNS     Soak cycle, test all sensors
1136   SHW%s CAM, TACHO, FLIP, DYE, LID, FRONT, MID, READ, BUTTON1, BUTTON2,
1137         SMART, TEMP, ON, OFF
1138   LNG%d 0/1/2/3/4/5 == ENG/POR/FRE/GER/SPA/ITA
1139   RUN%s CAM, FEED, DYE, MAIN, FLIPPER, FLIPROLL, FAN, PANEL, POUT, CAL, LCD,
1140         OFF
1141   FLM%s Y/M/C/K/O  Align ribbon at corresponding panel
1142   FCL     Init dye calibration routine
1143   FCL######  Set dye color to ###### (RGB hex)
1144 
1145 */
1146