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);
133129static unsigned find_quirks (int vendor_id , int product_id );
134130static 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 );
136133static int list_cb (usb_printer_t * printer , const char * device_uri ,
137134 const char * device_id , const void * data );
138135static 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