@@ -36,6 +36,9 @@ pub const PortInformation = struct {
36
36
description : []const u8 ,
37
37
manufacturer : []const u8 ,
38
38
serial_number : []const u8 ,
39
+ // TODO: review whether to remove `hw_id`.
40
+ // Is this useless/being used in a Windows-only way?
41
+ hw_id : []const u8 ,
39
42
vid : u16 ,
40
43
pid : u16 ,
41
44
};
@@ -172,6 +175,7 @@ const WindowsInformationIterator = struct {
172
175
defer self .index += 1 ;
173
176
174
177
var info : PortInformation = std .mem .zeroes (PortInformation );
178
+ @memset (& self .hw_id , 0 );
175
179
176
180
// NOTE: have not handled if port startswith("LPT")
177
181
var length = getPortName (& self .device_info_set , & device_info_data , & self .port_buffer );
@@ -192,10 +196,16 @@ const WindowsInformationIterator = struct {
192
196
self .device_info_set ,
193
197
& device_info_data ,
194
198
@ptrCast (& self .hw_id ),
195
- 256 ,
199
+ 255 ,
196
200
null ,
197
201
) == std .os .windows .TRUE ) {
198
- length = parseSerialNumber (device_info_data .devInst , & self .hw_id , & self .serial_buffer ) catch 0 ;
202
+ length = @as (u32 , @truncate (std .mem .indexOfSentinel (u8 , 0 , & self .hw_id )));
203
+ info .hw_id = self .hw_id [0.. length ];
204
+
205
+ length = parseSerialNumber (& self .hw_id , & self .serial_buffer ) catch 0 ;
206
+ if (length == 0 ) {
207
+ length = getParentSerialNumber (device_info_data .devInst , & self .hw_id , & self .serial_buffer ) catch 0 ;
208
+ }
199
209
info .serial_number = self .serial_buffer [0.. length ];
200
210
info .vid = parseVendorId (& self .hw_id ) catch 0 ;
201
211
info .pid = parseProductId (& self .hw_id ) catch 0 ;
@@ -220,8 +230,6 @@ const WindowsInformationIterator = struct {
220
230
_ = std .os .windows .advapi32 .RegCloseKey (hkey );
221
231
}
222
232
223
- // if (hkey == std.os.windows.INVALID_HANDLE_VALUE) return error.std.os.Windows;
224
-
225
233
inline for (.{ "PortName" , "PortNumber" }) | key_token | {
226
234
var port_length : std.os.windows.DWORD = std .os .windows .NAME_MAX ;
227
235
var data_type : std.os.windows.DWORD = 0 ;
@@ -266,38 +274,62 @@ const WindowsInformationIterator = struct {
266
274
return bytes_required ;
267
275
}
268
276
269
- fn parseSerialNumber (devinst : DEVINST , devid : []const u8 , serial_number : [* ]u8 ) ! std.os.windows.DWORD {
277
+ fn getParentSerialNumber (devinst : DEVINST , devid : []const u8 , serial_number : [* ]u8 ) ! std.os.windows.DWORD {
278
+ if (std .mem .startsWith (u8 , devid , "FTDI" )) {
279
+ // Should not be called on "FTDI" so just return the serial number.
280
+ return try parseSerialNumber (devid , serial_number );
281
+ } else if (std .mem .startsWith (u8 , devid , "USB" )) {
282
+ // taken from pyserial
283
+ const max_usb_device_tree_traversal_depth = 5 ;
284
+ const start_vidpid = std .mem .indexOf (u8 , devid , "VID" ) orelse return error .WindowsError ;
285
+ const vidpid_slice = devid [start_vidpid .. start_vidpid + 17 ]; // "VIDxxxx&PIDxxxx"
286
+
287
+ // keep looping over parent device to extract serial number if it contains the target VID and PID.
288
+ var depth : u8 = 0 ;
289
+ var child_inst : DEVINST = devinst ;
290
+ while (depth <= max_usb_device_tree_traversal_depth ) : (depth += 1 ) {
291
+ var parent_id : DEVINST = undefined ;
292
+ var local_buffer : [256 :0 ]u8 = std .mem .zeroes ([256 :0 ]u8 );
293
+
294
+ if (CM_Get_Parent (& parent_id , child_inst , 0 ) != 0 ) return error .WindowsError ;
295
+ if (CM_Get_Device_IDA (parent_id , @ptrCast (& local_buffer ), 256 , 0 ) != 0 ) return error .WindowsError ;
296
+ defer child_inst = parent_id ;
297
+
298
+ if (! std .mem .containsAtLeast (u8 , local_buffer [0.. 255], 1 , vidpid_slice )) continue ;
299
+
300
+ const length = try parseSerialNumber (local_buffer [0.. 255], serial_number );
301
+ if (length > 0 ) return length ;
302
+ }
303
+ }
304
+
305
+ return error .WindowsError ;
306
+ }
307
+
308
+ fn parseSerialNumber (devid : []const u8 , serial_number : [* ]u8 ) ! std.os.windows.DWORD {
270
309
var delimiter : ? []const u8 = undefined ;
271
- var slice : []const u8 = undefined ;
272
310
273
311
if (std .mem .startsWith (u8 , devid , "USB" )) {
274
312
delimiter = "\\ &" ;
275
-
276
- var parent_id : DEVINST = undefined ;
277
- var local_buffer : [256 :0 ]u8 = std .mem .zeroes ([256 :0 ]u8 );
278
-
279
- // It appears other approaches recursively scan through parent-child IDs to
280
- // find a serial number. This may need further investigation to understand
281
- // when/if this is required.
282
- if (CM_Get_Parent (& parent_id , devinst , 0 ) != 0 ) return error .WindowsError ;
283
- if (CM_Get_Device_IDA (parent_id , @ptrCast (& local_buffer ), 256 , 0 ) != 0 ) return error .WindowsError ;
284
-
285
- slice = local_buffer [0.. 256];
286
313
} else if (std .mem .startsWith (u8 , devid , "FTDI" )) {
287
314
delimiter = "\\ +" ;
288
- slice = devid ;
289
315
} else {
316
+ // What to do here?
290
317
delimiter = null ;
291
318
}
292
319
293
320
if (delimiter ) | del | {
294
- var it = std .mem .tokenize (u8 , slice , del );
321
+ var it = std .mem .tokenize (u8 , devid , del );
295
322
296
323
// throw away the start
297
324
_ = it .next ();
298
325
while (it .next ()) | segment | {
299
326
if (std .mem .startsWith (u8 , segment , "VID_" )) continue ;
300
327
if (std .mem .startsWith (u8 , segment , "PID_" )) continue ;
328
+
329
+ // If "MI_{d}{d}", this is an interface number. The serial number will have to be
330
+ // sourced from the parent node. Probably do not have to check all these conditions.
331
+ if (segment .len == 5 and std .mem .eql (u8 , "MI_" , segment [0.. 3]) and std .ascii .isDigit (segment [3 ]) and std .ascii .isDigit (segment [4 ])) return 0 ;
332
+
301
333
@memcpy (serial_number , segment );
302
334
return @as (std .os .windows .DWORD , @truncate (segment .len ));
303
335
}
0 commit comments