Skip to content

Commit 83bc0de

Browse files
committed
Fix DYMO USB serial number support (Issue #1338)
1 parent 8b68a94 commit 83bc0de

2 files changed

Lines changed: 122 additions & 50 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Changes in CUPS v2.4.13 (YYYY-MM-DD)
2323
- Fixed scheduler freezing with zombie clients (Issue #1264)
2424
- Fixed support for the server name in the ErrorLog filename (Issue #1277)
2525
- Fixed job cleanup after daemon restart (Issue #1315)
26+
- Fixed handling of buggy DYMO USB printer serial numbers (Issue #1338)
2627

2728

2829
Changes in CUPS v2.4.12 (2025-04-08)

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);
@@ -1105,6 +1102,116 @@ get_device_id(usb_printer_t *printer, /* I - Printer */
11051102
}
11061103

11071104

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

12701375

@@ -1276,33 +1381,8 @@ make_device_uri(
12761381

12771382
memset(&devdesc, 0, sizeof(devdesc));
12781383

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

13071387
if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL)
13081388
{
@@ -1390,21 +1470,12 @@ make_device_uri(
13901470
* and interface number...
13911471
*/
13921472

1393-
if (sern)
1394-
{
1395-
if (printer->iface > 0)
1396-
snprintf(options, sizeof(options), "?serial=%s&interface=%d", sern,
1397-
printer->iface);
1398-
else
1399-
snprintf(options, sizeof(options), "?serial=%s", sern);
1400-
}
1401-
else if (printer->iface > 0)
1402-
snprintf(options, sizeof(options), "?interface=%d", printer->iface);
1473+
if (printer->iface > 0)
1474+
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1475+
"/%s?serial=%s&interface=%d", mdl, sern, printer->iface);
14031476
else
1404-
options[0] = '\0';
1405-
1406-
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1407-
"/%s%s", mdl, options);
1477+
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
1478+
"/%s?serial=%s", mdl, sern);
14081479

14091480
cupsFreeOptions(num_values, values);
14101481

0 commit comments

Comments
 (0)