@@ -54,6 +54,9 @@ def qbool(value):
54
54
55
55
56
56
class DeviceSerializer :
57
+ """
58
+ Group of method for serialization of device properties.
59
+ """
57
60
ALLOWED_CHARS_KEY = set (
58
61
string .digits + string .ascii_letters
59
62
+ r"!#$%&()*+,-./:;<>?@[\]^_{|}~" )
@@ -177,7 +180,7 @@ def parse_basic_device_properties(
177
180
f"Unrecognized device identity '{ properties ['device_id' ]} ' "
178
181
f"expected '{ expected_device .device_id } '"
179
182
)
180
- expected . _device_id = properties .get ('device_id' , expected_devid )
183
+ properties [ 'device_id' ] = properties .get ('device_id' , expected_devid )
181
184
182
185
properties ['port' ] = expected
183
186
@@ -205,8 +208,8 @@ def sanitize_str(
205
208
"""
206
209
Sanitize given untrusted string.
207
210
208
- If `replace_char` is not None, ignore `error_message` and replace invalid
209
- characters with the string.
211
+ If `replace_char` is not None, ignore `error_message` and replace
212
+ invalid characters with the string.
210
213
"""
211
214
if replace_char is None :
212
215
not_allowed_chars = set (untrusted_value ) - allowed_chars
@@ -229,7 +232,7 @@ class Port:
229
232
Attributes:
230
233
backend_domain (QubesVM): The domain which exposes devices,
231
234
e.g.`sys-usb`.
232
- port_id (str): A unique identifier for the port within the backend domain .
235
+ port_id (str): A unique (in backend domain) identifier for the port .
233
236
devclass (str): The class of the port (e.g., 'usb', 'pci').
234
237
"""
235
238
def __init__ (self , backend_domain , port_id , devclass ):
@@ -264,6 +267,7 @@ def __str__(self):
264
267
265
268
@property
266
269
def backend_name (self ) -> str :
270
+ # pylint: disable=missing-function-docstring
267
271
if self .backend_domain not in (None , "*" ):
268
272
return self .backend_domain .name
269
273
return "*"
@@ -272,6 +276,9 @@ def backend_name(self) -> str:
272
276
def from_qarg (
273
277
cls , representation : str , devclass , domains , blind = False
274
278
) -> 'Port' :
279
+ """
280
+ Parse qrexec argument <back_vm>+<port_id> to retrieve Port.
281
+ """
275
282
if blind :
276
283
get_domain = domains .get_blind
277
284
else :
@@ -282,6 +289,9 @@ def from_qarg(
282
289
def from_str (
283
290
cls , representation : str , devclass , domains , blind = False
284
291
) -> 'Port' :
292
+ """
293
+ Parse string <back_vm>:<port_id> to retrieve Port.
294
+ """
285
295
if blind :
286
296
get_domain = domains .get_blind
287
297
else :
@@ -296,6 +306,9 @@ def _parse(
296
306
get_domain : Callable ,
297
307
sep : str
298
308
) -> 'Port' :
309
+ """
310
+ Parse string representation and return instance of Port.
311
+ """
299
312
backend_name , port_id = representation .split (sep , 1 )
300
313
backend = get_domain (backend_name )
301
314
return cls (backend_domain = backend , port_id = port_id , devclass = devclass )
@@ -344,7 +357,7 @@ def __init__(
344
357
self .port : Optional [Port ] = port
345
358
self ._device_id = device_id
346
359
347
- def clone (self , ** kwargs ):
360
+ def clone (self , ** kwargs ) -> 'VirtualDevice' :
348
361
"""
349
362
Clone object and substitute attributes with explicitly given.
350
363
"""
@@ -353,48 +366,67 @@ def clone(self, **kwargs):
353
366
"device_id" : self .device_id ,
354
367
}
355
368
attr .update (kwargs )
356
- return self . __class__ (** attr )
369
+ return VirtualDevice (** attr )
357
370
358
371
@property
359
- def port (self ):
372
+ def port (self ) -> Union [Port , str ]:
373
+ # pylint: disable=missing-function-docstring
360
374
return self ._port
361
375
362
376
@port .setter
363
- def port (self , value ):
377
+ def port (self , value : Union [Port , str , None ]):
378
+ # pylint: disable=missing-function-docstring
364
379
self ._port = value if value is not None else '*'
365
380
366
381
@property
367
- def device_id (self ):
382
+ def device_id (self ) -> str :
383
+ # pylint: disable=missing-function-docstring
368
384
if self ._device_id is not None :
369
385
return self ._device_id
370
386
return '*'
371
387
372
388
@property
373
- def backend_domain (self ):
389
+ def is_device_id_set (self ) -> bool :
390
+ """
391
+ Check if `device_id` is explicitly set.
392
+ """
393
+ return self ._device_id is not None
394
+
395
+ @property
396
+ def backend_domain (self ) -> Union [QubesVM , str ]:
397
+ # pylint: disable=missing-function-docstring
374
398
if self .port != '*' and self .port .backend_domain is not None :
375
399
return self .port .backend_domain
376
400
return '*'
377
401
378
402
@property
379
- def backend_name (self ):
403
+ def backend_name (self ) -> str :
404
+ """
405
+ Return backend domain name if any or `*`.
406
+ """
380
407
if self .port != '*' :
381
408
return self .port .backend_name
382
409
return '*'
383
410
384
411
@property
385
- def port_id (self ):
412
+ def port_id (self ) -> str :
413
+ # pylint: disable=missing-function-docstring
386
414
if self .port != '*' and self .port .port_id is not None :
387
415
return self .port .port_id
388
416
return '*'
389
417
390
418
@property
391
- def devclass (self ):
419
+ def devclass (self ) -> str :
420
+ # pylint: disable=missing-function-docstring
392
421
if self .port != '*' and self .port .devclass is not None :
393
422
return self .port .devclass
394
423
return '*'
395
424
396
425
@property
397
- def description (self ):
426
+ def description (self ) -> str :
427
+ """
428
+ Return human-readable description of the device identity.
429
+ """
398
430
if self .device_id == '*' :
399
431
return 'any device'
400
432
return self .device_id
@@ -431,17 +463,16 @@ def __lt__(self, other):
431
463
if self .port != '*' and other .port == '*' :
432
464
return False
433
465
reprs = {self : [self .port ], other : [other .port ]}
434
- for obj in reprs :
466
+ for obj , obj_repr in reprs . items () :
435
467
if obj .device_id != '*' :
436
- reprs [ obj ] .append (obj .device_id )
468
+ obj_repr .append (obj .device_id )
437
469
return reprs [self ] < reprs [other ]
438
- elif isinstance (other , Port ):
470
+ if isinstance (other , Port ):
439
471
_other = VirtualDevice (other , '*' )
440
472
return self < _other
441
- else :
442
- raise TypeError (
443
- f"Comparing instances of { type (self )} and '{ type (other )} ' "
444
- "is not supported" )
473
+ raise TypeError (
474
+ f"Comparing instances of { type (self )} and '{ type (other )} ' "
475
+ "is not supported" )
445
476
446
477
def __repr__ (self ):
447
478
return f"{ self .port !r} :{ self .device_id } "
@@ -458,6 +489,9 @@ def from_qarg(
458
489
blind = False ,
459
490
backend = None ,
460
491
) -> 'VirtualDevice' :
492
+ """
493
+ Parse qrexec argument <back_vm>+<port_id>:<device_id> to get device info
494
+ """
461
495
if backend is None :
462
496
if blind :
463
497
get_domain = domains .get_blind
@@ -472,6 +506,9 @@ def from_str(
472
506
cls , representation : str , devclass : Optional [str ], domains ,
473
507
blind = False , backend = None
474
508
) -> 'VirtualDevice' :
509
+ """
510
+ Parse string <back_vm>+<port_id>:<device_id> to get device info
511
+ """
475
512
if backend is None :
476
513
if blind :
477
514
get_domain = domains .get_blind
@@ -490,6 +527,9 @@ def _parse(
490
527
backend ,
491
528
sep : str
492
529
) -> 'VirtualDevice' :
530
+ """
531
+ Parse string representation and return instance of VirtualDevice.
532
+ """
493
533
if backend is None :
494
534
backend_name , identity = representation .split (sep , 1 )
495
535
if backend_name != '*' :
@@ -701,14 +741,23 @@ def _load_classes(bus: str):
701
741
return result
702
742
703
743
def matches (self , other : 'DeviceInterface' ) -> bool :
744
+ """
745
+ Check if this `DeviceInterface` (pattern) matches given one.
746
+
747
+ The matching is done character by character using the string
748
+ representation (`repr`) of both objects. A wildcard character (`'*'`)
749
+ in the pattern (i.e., `self`) can match any character in the candidate
750
+ (i.e., `other`).
751
+ The two representations must be of the same length.
752
+ """
704
753
pattern = repr (self )
705
754
candidate = repr (other )
706
755
if len (pattern ) != len (candidate ):
707
756
return False
708
- for p , c in zip (pattern , candidate ):
709
- if p == '*' :
757
+ for patt , cand in zip (pattern , candidate ):
758
+ if patt == '*' :
710
759
continue
711
- if p != c :
760
+ if patt != cand :
712
761
return False
713
762
return True
714
763
@@ -909,7 +958,8 @@ def serialize(self) -> bytes:
909
958
'parent_devclass' , self .parent_device .devclass )
910
959
911
960
for key , value in self .data .items ():
912
- properties += b' ' + DeviceSerializer .pack_property ("_" + key , value )
961
+ properties += b' ' + DeviceSerializer .pack_property (
962
+ "_" + key , value )
913
963
914
964
return properties
915
965
@@ -932,6 +982,7 @@ def deserialize(
932
982
device = cls ._deserialize (rest , device )
933
983
# pylint: disable=broad-exception-caught
934
984
except Exception as exc :
985
+ print (str (exc ), file = sys .stderr )
935
986
device = UnknownDevice .from_device (device )
936
987
937
988
return device
@@ -1006,36 +1057,28 @@ def device_id(self, value):
1006
1057
class UnknownDevice (DeviceInfo ):
1007
1058
# pylint: disable=too-few-public-methods
1008
1059
"""Unknown device - for example, exposed by domain not running currently"""
1060
+
1009
1061
@staticmethod
1010
- def from_device (device ) -> 'UnknownDevice' :
1062
+ def from_device (device : VirtualDevice ) -> 'UnknownDevice' :
1063
+ """
1064
+ Return `UnknownDevice` based on any virtual device.
1065
+ """
1011
1066
return UnknownDevice (device .port , device_id = device .device_id )
1012
1067
1013
1068
1014
1069
class AssignmentMode (Enum ):
1070
+ """
1071
+ Device assignment modes
1072
+ """
1015
1073
MANUAL = "manual"
1016
1074
ASK = "ask-to-attach"
1017
1075
AUTO = "auto-attach"
1018
1076
REQUIRED = "required"
1019
1077
1020
1078
1021
1079
class DeviceAssignment :
1022
- """ Maps a device to a frontend_domain.
1023
-
1024
- There are 3 flags `attached`, `automatically_attached` and `required`.
1025
- The meaning of valid combinations is as follows:
1026
- 1. (True, False, False) -> domain is running, device is manually attached
1027
- and could be manually detach any time.
1028
- 2. (True, True, False) -> domain is running, device is attached
1029
- and could be manually detach any time (see 4.),
1030
- but in the future will be auto-attached again.
1031
- 3. (True, True, True) -> domain is running, device is attached
1032
- and couldn't be detached.
1033
- 4. (False, Ture, False) -> device is assigned to domain, but not attached
1034
- because either (i) domain is halted,
1035
- device (ii) manually detached or
1036
- (iii) attach to different domain.
1037
- 5. (False, True, True) -> domain is halted, device assigned to domain
1038
- and required to start domain.
1080
+ """
1081
+ Maps a device to a frontend_domain.
1039
1082
"""
1040
1083
1041
1084
def __init__ (
@@ -1096,28 +1139,33 @@ def __lt__(self, other):
1096
1139
"is not supported" )
1097
1140
1098
1141
@property
1099
- def backend_domain (self ):
1142
+ def backend_domain (self ) -> QubesVM :
1143
+ # pylint: disable=missing-function-docstring
1100
1144
return self .virtual_device .backend_domain
1101
1145
1102
1146
@property
1103
1147
def backend_name (self ) -> str :
1148
+ # pylint: disable=missing-function-docstring
1104
1149
return self .virtual_device .backend_name
1105
1150
1106
1151
@property
1107
- def port_id (self ):
1152
+ def port_id (self ) -> str :
1153
+ # pylint: disable=missing-function-docstring
1108
1154
return self .virtual_device .port_id
1109
1155
1110
1156
@property
1111
- def devclass (self ):
1157
+ def devclass (self ) -> str :
1158
+ # pylint: disable=missing-function-docstring
1112
1159
return self .virtual_device .devclass
1113
1160
1114
1161
@property
1115
- def device_id (self ):
1162
+ def device_id (self ) -> str :
1163
+ # pylint: disable=missing-function-docstring
1116
1164
return self .virtual_device .device_id
1117
1165
1118
1166
@property
1119
1167
def devices (self ) -> List [DeviceInfo ]:
1120
- """Get DeviceInfo object corresponding to this DeviceAssignment"""
1168
+ """Get DeviceInfo objects corresponding to this DeviceAssignment"""
1121
1169
if self .port_id != '*' :
1122
1170
# could return UnknownDevice
1123
1171
return [self .backend_domain .devices [self .devclass ][self .port_id ]]
@@ -1221,7 +1269,8 @@ def serialize(self) -> bytes:
1221
1269
'frontend_domain' , self .frontend_domain .name )
1222
1270
1223
1271
for key , value in self .options .items ():
1224
- properties += b' ' + DeviceSerializer .pack_property ("_" + key , value )
1272
+ properties += b' ' + DeviceSerializer .pack_property (
1273
+ "_" + key , value )
1225
1274
1226
1275
return properties
1227
1276
@@ -1255,22 +1304,26 @@ def _deserialize(
1255
1304
1256
1305
DeviceSerializer .parse_basic_device_properties (
1257
1306
expected_device , properties )
1307
+
1308
+ expected_device = expected_device .clone (
1309
+ device_id = properties ['device_id' ])
1258
1310
# we do not need port, we need device
1259
1311
del properties ['port' ]
1260
- expected_device ._device_id = properties .get (
1261
- 'device_id' , expected_device .device_id )
1262
1312
properties .pop ('device_id' , None )
1263
1313
properties ['device' ] = expected_device
1264
1314
1265
1315
return cls (** properties )
1266
1316
1267
1317
def matches (self , device : VirtualDevice ) -> bool :
1318
+ """
1319
+ Checks if the given device matches the assignment.
1320
+ """
1268
1321
if self .devclass != device .devclass :
1269
1322
return False
1270
1323
if self .backend_domain != device .backend_domain :
1271
1324
return False
1272
- if self .port_id != '*' and self . port_id != device .port_id :
1325
+ if self .port_id not in ( '*' , device .port_id ) :
1273
1326
return False
1274
- if self .device_id != '*' and self . device_id != device .device_id :
1327
+ if self .device_id not in ( '*' , device .device_id ) :
1275
1328
return False
1276
1329
return True
0 commit comments