Skip to content

Commit 75c15d1

Browse files
committed
Fix DYMO USB serial number support (Issue #1338)
1 parent 39b981b commit 75c15d1

1 file changed

Lines changed: 121 additions & 50 deletions

File tree

backend/usb-libusb.c

Lines changed: 121 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
/*
22
* LIBUSB interface code for CUPS.
33
*
4-
* Copyright © 2020-2024 by OpenPrinting.
4+
* Copyright © 2020-2025 by OpenPrinting.
55
* Copyright © 2007-2019 by Apple Inc.
66
*
77
* Licensed under Apache License v2.0. See the file "LICENSE" for more
88
* information.
99
*/
1010

11-
/*
12-
* Include necessary headers...
13-
*/
14-
1511
#include <libusb.h>
1612
#include <cups/cups-private.h>
1713
#include <cups/ppd-private.h>
@@ -133,6 +129,7 @@ static usb_printer_t *find_device(usb_cb_t cb, const void *data);
133129
static unsigned find_quirks(int vendor_id, int product_id);
134130
static int get_device_id(usb_printer_t *printer, char *buffer,
135131
size_t bufsize);
132+
static void get_serial_number(usb_printer_t *printer, uint8_t desc_index, char *buffer, size_t bufsize);
136133
static int list_cb(usb_printer_t *printer, const char *device_uri,
137134
const char *device_id, const void *data);
138135
static void load_quirks(void);
@@ -1108,6 +1105,116 @@ get_device_id(usb_printer_t *printer, /* I - Printer */
11081105
}
11091106

11101107

1108+
/*
1109+
* 'get_serial_number()' - Get the USB device serial number.
1110+
*
1111+
* This function is necessary because some vendors (DYMO, others) don't know
1112+
* how to implement USB correctly and having a unique serial number is necessary
1113+
* to support connecting more than one USB printer of the same make and model.
1114+
*
1115+
* The first bit of this code duplicates the strategy employed by
1116+
* `libusb_get_string_descriptor_ascii()` - get the list of supported language
1117+
* IDs and use the first (and usually only) language ID (almost always US
1118+
* English or 0x0409) to get the specified iSerialNumber string descriptor as
1119+
* a series of 16-bit UCS-2 Little Endian characters - this word order is
1120+
* mandated in section 8.1 of the USB 2.0 specification. The libusb function
1121+
* then copies the string, replacing any characters greater than 127 with '?'
1122+
* and happily embedding any non-printable ASCII characters such as NULs.
1123+
*
1124+
* In the case of DYMO printers, the iSerialNumber string consists of the
1125+
* U+3030 ("Wavy Dash") character followed by the ASCII serial number digits
1126+
* as 16-bit *Big Endian* characters. Acknowledging that USB implementors have
1127+
* proven capable of making lots of mistakes like this, this function takes a
1128+
* more pragmatic approach and converts serial number descriptors to hexadecimal
1129+
* if they don't contain purely printable US ASCII characters. This preserves
1130+
* backwards compatibility with conforming printers while allowing non-
1131+
* conforming printers to work reliably for the first time.
1132+
*
1133+
* If we are not able to get a serial number at all (`desc_index` is 0 or the
1134+
* other calls fail), then we fall back on using the configuration and interface
1135+
* indices from libusb, as before.
1136+
*
1137+
* (This code adapted with permission from PAPPL project)
1138+
*/
1139+
1140+
static void
1141+
get_serial_number(
1142+
usb_printer_t *printer, /* I - Printer */
1143+
uint8_t desc_index, /* I - Serial number descriptor index */
1144+
char *buffer, /* I - String buffer */
1145+
size_t bufsize) /* I - Number of bytes in buffer */
1146+
{
1147+
uint8_t langbuf[4]; // Language code buffer
1148+
uint16_t langid; // Language code/ID
1149+
uint8_t snbuf[256]; // Raw serial number buffer
1150+
int snlen; // Length of response
1151+
uint16_t snchar; // Character from raw serial number buffer
1152+
int i; // Looping var
1153+
char *bufptr, // Pointer into string buffer
1154+
*bufend; // End of string buffer
1155+
1156+
1157+
// If there is no serial number string, fallback...
1158+
if (!desc_index)
1159+
goto fallback;
1160+
1161+
// Get the first supported language code...
1162+
if (libusb_get_string_descriptor(printer->device, 0, 0, langbuf, sizeof(langbuf)) < 4)
1163+
goto fallback; // Didn't get 4 bytes
1164+
else if (langbuf[0] < 4 || (langbuf[0] & 1))
1165+
goto fallback; // Bad length
1166+
else if (langbuf[1] != LIBUSB_DT_STRING)
1167+
goto fallback; // Not a string
1168+
1169+
langid = langbuf[2] | (langbuf[3] << 8);
1170+
1171+
// Then try to get the serial number string...
1172+
if ((snlen = libusb_get_string_descriptor(printer->device, desc_index, langid, snbuf, sizeof(snbuf))) < 10)
1173+
goto fallback; // Didn't get at least 10 bytes
1174+
else if (snbuf[0] != snlen || (snbuf[0] & 1))
1175+
goto fallback; // Bad length
1176+
else if (snbuf[1] != LIBUSB_DT_STRING)
1177+
goto fallback; // Not a string
1178+
1179+
// Loop through the string to determine whether it is valid...
1180+
for (i = 2, bufptr = buffer, bufend = buffer + bufsize - 1; i < snlen && bufptr < bufend; i += 2)
1181+
{
1182+
// Get the current UCS-2 character...
1183+
snchar = snbuf[i] | (snbuf[i + 1] << 8);
1184+
1185+
// Abort if not printable ASCII...
1186+
if (snchar < 0x20 || snchar >= 0x7f)
1187+
break;
1188+
1189+
// Otherwise copy...
1190+
*bufptr++ = (char)snchar;
1191+
}
1192+
1193+
if (i >= snlen)
1194+
{
1195+
// Got a good string, return it...
1196+
*bufptr = '\0';
1197+
return;
1198+
}
1199+
1200+
// Convert string to HEX...
1201+
for (i = 2, bufptr = buffer, bufend = buffer + bufsize - 1; i < snlen && bufptr < bufend; i ++, bufptr += 2)
1202+
{
1203+
snprintf(bufptr, (size_t)(bufend - bufptr + 1), "%02X", snbuf[i]);
1204+
}
1205+
1206+
if (i >= snlen)
1207+
return; // Converted all bytes to HEX...
1208+
1209+
1210+
// If we get here then we were not able to get a serial number string at all
1211+
// and have to hope that the bus and interface indices will be enough...
1212+
fallback:
1213+
1214+
snprintf(buffer, bufsize, "%d.%d", printer->conf, printer->iface);
1215+
}
1216+
1217+
11111218
/*
11121219
* 'list_cb()' - List USB printers for discovery.
11131220
*/
@@ -1257,17 +1364,15 @@ make_device_uri(
12571364
{
12581365
struct libusb_device_descriptor devdesc;
12591366
/* Current device descriptor */
1260-
char options[1024]; /* Device URI options */
12611367
int num_values; /* Number of 1284 parameters */
12621368
cups_option_t *values; /* 1284 parameters */
12631369
const char *mfg, /* Manufacturer */
12641370
*mdl, /* Model */
1265-
*des = NULL, /* Description */
1266-
*sern = NULL; /* Serial number */
1371+
*des = NULL; /* Description */
12671372
size_t mfglen; /* Length of manufacturer string */
12681373
char tempmdl[256], /* Temporary model string */
12691374
tempmfg[256], /* Temporary manufacturer string */
1270-
tempsern[256], /* Temporary serial number string */
1375+
sern[256], /* Serial number string */
12711376
*tempptr; /* Pointer into temp string */
12721377

12731378

@@ -1279,33 +1384,8 @@ make_device_uri(
12791384

12801385
memset(&devdesc, 0, sizeof(devdesc));
12811386

1282-
if (libusb_get_device_descriptor(printer->device, &devdesc) >= 0 && devdesc.iSerialNumber)
1283-
{
1284-
// Try getting the serial number from the device itself...
1285-
int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iSerialNumber, (unsigned char *)tempsern, sizeof(tempsern) - 1);
1286-
if (length > 0)
1287-
{
1288-
tempsern[length] = '\0';
1289-
sern = tempsern;
1290-
1291-
fprintf(stderr, "DEBUG2: iSerialNumber=\"%s\"\n", tempsern);
1292-
}
1293-
else
1294-
fputs("DEBUG2: iSerialNumber could not be read.\n", stderr);
1295-
}
1296-
else
1297-
fputs("DEBUG2: iSerialNumber is not present.\n", stderr);
1298-
1299-
#if 0
1300-
if (!sern)
1301-
{
1302-
// Fall back on serial number from IEEE-1284 device ID, which on some
1303-
// printers (Issue #170) is a bogus hardcoded number.
1304-
if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
1305-
if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
1306-
sern = cupsGetOption("SN", num_values, values);
1307-
}
1308-
#endif // 0
1387+
libusb_get_device_descriptor(printer->device, &devdesc);
1388+
get_serial_number(printer, devdesc.iSerialNumber, sern, sizeof(sern));
13091389

13101390
if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL)
13111391
{
@@ -1393,21 +1473,12 @@ make_device_uri(
13931473
* and interface number...
13941474
*/
13951475

1396-
if (sern)
1397-
{
1398-
if (printer->iface > 0)
1399-
snprintf(options, sizeof(options), "?serial=%s&interface=%d", sern,
1400-
printer->iface);
1401-
else
1402-
snprintf(options, sizeof(options), "?serial=%s", sern);
1403-
}
1404-
else if (printer->iface > 0)
1405-
snprintf(options, sizeof(options), "?interface=%d", printer->iface);
1476+
if (printer->iface > 0)
1477+
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1478+
"/%s?serial=%s&interface=%d", mdl, sern, printer->iface);
14061479
else
1407-
options[0] = '\0';
1408-
1409-
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1410-
"/%s%s", mdl, options);
1480+
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1481+
"/%s?serial=%s", mdl, sern);
14111482

14121483
cupsFreeOptions(num_values, values);
14131484

0 commit comments

Comments
 (0)