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 );
@@ -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