@@ -74,6 +74,9 @@ def qbool(value):
74
74
75
75
76
76
class DeviceSerializer :
77
+ """
78
+ Group of method for serialization of device properties.
79
+ """
77
80
ALLOWED_CHARS_KEY = set (
78
81
string .digits + string .ascii_letters
79
82
+ r"!#$%&()*+,-./:;<>?@[\]^_{|}~" )
@@ -197,7 +200,7 @@ def parse_basic_device_properties(
197
200
f"Unrecognized device identity '{ properties ['device_id' ]} ' "
198
201
f"expected '{ expected_device .device_id } '"
199
202
)
200
- expected . _device_id = properties .get ('device_id' , expected_devid )
203
+ properties [ 'device_id' ] = properties .get ('device_id' , expected_devid )
201
204
202
205
properties ['port' ] = expected
203
206
@@ -225,8 +228,8 @@ def sanitize_str(
225
228
"""
226
229
Sanitize given untrusted string.
227
230
228
- If `replace_char` is not None, ignore `error_message` and replace invalid
229
- characters with the string.
231
+ If `replace_char` is not None, ignore `error_message` and replace
232
+ invalid characters with the string.
230
233
"""
231
234
if replace_char is None :
232
235
not_allowed_chars = set (untrusted_value ) - allowed_chars
@@ -249,7 +252,7 @@ class Port:
249
252
Attributes:
250
253
backend_domain (QubesVM): The domain which exposes devices,
251
254
e.g.`sys-usb`.
252
- port_id (str): A unique identifier for the port within the backend domain .
255
+ port_id (str): A unique (in backend domain) identifier for the port .
253
256
devclass (str): The class of the port (e.g., 'usb', 'pci').
254
257
"""
255
258
def __init__ (self , backend_domain , port_id , devclass ):
@@ -284,6 +287,7 @@ def __str__(self):
284
287
285
288
@property
286
289
def backend_name (self ) -> str :
290
+ # pylint: disable=missing-function-docstring
287
291
if self .backend_domain not in (None , "*" ):
288
292
return self .backend_domain .name
289
293
return "*"
@@ -292,6 +296,9 @@ def backend_name(self) -> str:
292
296
def from_qarg (
293
297
cls , representation : str , devclass , domains , blind = False
294
298
) -> 'Port' :
299
+ """
300
+ Parse qrexec argument <back_vm>+<port_id> to retrieve Port.
301
+ """
295
302
if blind :
296
303
get_domain = domains .get_blind
297
304
else :
@@ -302,6 +309,9 @@ def from_qarg(
302
309
def from_str (
303
310
cls , representation : str , devclass , domains , blind = False
304
311
) -> 'Port' :
312
+ """
313
+ Parse string <back_vm>:<port_id> to retrieve Port.
314
+ """
305
315
if blind :
306
316
get_domain = domains .get_blind
307
317
else :
@@ -316,6 +326,9 @@ def _parse(
316
326
get_domain : Callable ,
317
327
sep : str
318
328
) -> 'Port' :
329
+ """
330
+ Parse string representation and return instance of Port.
331
+ """
319
332
backend_name , port_id = representation .split (sep , 1 )
320
333
backend = get_domain (backend_name )
321
334
return cls (backend_domain = backend , port_id = port_id , devclass = devclass )
@@ -364,7 +377,7 @@ def __init__(
364
377
self .port : Optional [Port ] = port
365
378
self ._device_id = device_id
366
379
367
- def clone (self , ** kwargs ):
380
+ def clone (self , ** kwargs ) -> 'VirtualDevice' :
368
381
"""
369
382
Clone object and substitute attributes with explicitly given.
370
383
"""
@@ -376,45 +389,64 @@ def clone(self, **kwargs):
376
389
return self .__class__ (** attr )
377
390
378
391
@property
379
- def port (self ):
392
+ def port (self ) -> Union [Port , str ]:
393
+ # pylint: disable=missing-function-docstring
380
394
return self ._port
381
395
382
396
@port .setter
383
- def port (self , value ):
397
+ def port (self , value : Union [Port , str , None ]):
398
+ # pylint: disable=missing-function-docstring
384
399
self ._port = value if value is not None else '*'
385
400
386
401
@property
387
- def device_id (self ):
402
+ def device_id (self ) -> str :
403
+ # pylint: disable=missing-function-docstring
388
404
if self ._device_id is not None :
389
405
return self ._device_id
390
406
return '*'
391
407
392
408
@property
393
- def backend_domain (self ):
409
+ def is_device_id_set (self ) -> bool :
410
+ """
411
+ Check if `device_id` is explicitly set.
412
+ """
413
+ return self ._device_id is not None
414
+
415
+ @property
416
+ def backend_domain (self ) -> Union [QubesVM , str ]:
417
+ # pylint: disable=missing-function-docstring
394
418
if self .port != '*' and self .port .backend_domain is not None :
395
419
return self .port .backend_domain
396
420
return '*'
397
421
398
422
@property
399
- def backend_name (self ):
423
+ def backend_name (self ) -> str :
424
+ """
425
+ Return backend domain name if any or `*`.
426
+ """
400
427
if self .port != '*' :
401
428
return self .port .backend_name
402
429
return '*'
403
430
404
431
@property
405
- def port_id (self ):
432
+ def port_id (self ) -> str :
433
+ # pylint: disable=missing-function-docstring
406
434
if self .port != '*' and self .port .port_id is not None :
407
435
return self .port .port_id
408
436
return '*'
409
437
410
438
@property
411
- def devclass (self ):
439
+ def devclass (self ) -> str :
440
+ # pylint: disable=missing-function-docstring
412
441
if self .port != '*' and self .port .devclass is not None :
413
442
return self .port .devclass
414
443
return '*'
415
444
416
445
@property
417
- def description (self ):
446
+ def description (self ) -> str :
447
+ """
448
+ Return human-readable description of the device identity.
449
+ """
418
450
if self .device_id == '*' :
419
451
return 'any device'
420
452
return self .device_id
@@ -451,17 +483,16 @@ def __lt__(self, other):
451
483
if self .port != '*' and other .port == '*' :
452
484
return False
453
485
reprs = {self : [self .port ], other : [other .port ]}
454
- for obj in reprs :
486
+ for obj , obj_repr in reprs . items () :
455
487
if obj .device_id != '*' :
456
- reprs [ obj ] .append (obj .device_id )
488
+ obj_repr .append (obj .device_id )
457
489
return reprs [self ] < reprs [other ]
458
- elif isinstance (other , Port ):
490
+ if isinstance (other , Port ):
459
491
_other = VirtualDevice (other , '*' )
460
492
return self < _other
461
- else :
462
- raise TypeError (
463
- f"Comparing instances of { type (self )} and '{ type (other )} ' "
464
- "is not supported" )
493
+ raise TypeError (
494
+ f"Comparing instances of { type (self )} and '{ type (other )} ' "
495
+ "is not supported" )
465
496
466
497
def __repr__ (self ):
467
498
return f"{ self .port !r} :{ self .device_id } "
@@ -478,6 +509,9 @@ def from_qarg(
478
509
blind = False ,
479
510
backend = None ,
480
511
) -> 'VirtualDevice' :
512
+ """
513
+ Parse qrexec argument <back_vm>+<port_id>:<device_id> to get device info
514
+ """
481
515
if backend is None :
482
516
if blind :
483
517
get_domain = domains .get_blind
@@ -492,6 +526,9 @@ def from_str(
492
526
cls , representation : str , devclass : Optional [str ], domains ,
493
527
blind = False , backend = None
494
528
) -> 'VirtualDevice' :
529
+ """
530
+ Parse string <back_vm>+<port_id>:<device_id> to get device info
531
+ """
495
532
if backend is None :
496
533
if blind :
497
534
get_domain = domains .get_blind
@@ -510,6 +547,9 @@ def _parse(
510
547
backend ,
511
548
sep : str
512
549
) -> 'VirtualDevice' :
550
+ """
551
+ Parse string representation and return instance of VirtualDevice.
552
+ """
513
553
if backend is None :
514
554
backend_name , identity = representation .split (sep , 1 )
515
555
if backend_name != '*' :
@@ -721,14 +761,23 @@ def _load_classes(bus: str):
721
761
return result
722
762
723
763
def matches (self , other : 'DeviceInterface' ) -> bool :
764
+ """
765
+ Check if this `DeviceInterface` (pattern) matches given one.
766
+
767
+ The matching is done character by character using the string
768
+ representation (`repr`) of both objects. A wildcard character (`'*'`)
769
+ in the pattern (i.e., `self`) can match any character in the candidate
770
+ (i.e., `other`).
771
+ The two representations must be of the same length.
772
+ """
724
773
pattern = repr (self )
725
774
candidate = repr (other )
726
775
if len (pattern ) != len (candidate ):
727
776
return False
728
- for p , c in zip (pattern , candidate ):
729
- if p == '*' :
777
+ for patt , cand in zip (pattern , candidate ):
778
+ if patt == '*' :
730
779
continue
731
- if p != c :
780
+ if patt != cand :
732
781
return False
733
782
return True
734
783
@@ -929,7 +978,8 @@ def serialize(self) -> bytes:
929
978
'parent_devclass' , self .parent_device .devclass )
930
979
931
980
for key , value in self .data .items ():
932
- properties += b' ' + DeviceSerializer .pack_property ("_" + key , value )
981
+ properties += b' ' + DeviceSerializer .pack_property (
982
+ "_" + key , value )
933
983
934
984
return properties
935
985
@@ -952,6 +1002,7 @@ def deserialize(
952
1002
device = cls ._deserialize (rest , device )
953
1003
# pylint: disable=broad-exception-caught
954
1004
except Exception as exc :
1005
+ print (str (exc ), file = sys .stderr )
955
1006
device = UnknownDevice .from_device (device )
956
1007
957
1008
return device
@@ -1026,36 +1077,28 @@ def device_id(self, value):
1026
1077
class UnknownDevice (DeviceInfo ):
1027
1078
# pylint: disable=too-few-public-methods
1028
1079
"""Unknown device - for example, exposed by domain not running currently"""
1080
+
1029
1081
@staticmethod
1030
- def from_device (device ) -> 'UnknownDevice' :
1082
+ def from_device (device : VirtualDevice ) -> 'UnknownDevice' :
1083
+ """
1084
+ Return `UnknownDevice` based on any virtual device.
1085
+ """
1031
1086
return UnknownDevice (device .port , device_id = device .device_id )
1032
1087
1033
1088
1034
1089
class AssignmentMode (Enum ):
1090
+ """
1091
+ Device assignment modes
1092
+ """
1035
1093
MANUAL = "manual"
1036
1094
ASK = "ask-to-attach"
1037
1095
AUTO = "auto-attach"
1038
1096
REQUIRED = "required"
1039
1097
1040
1098
1041
1099
class DeviceAssignment :
1042
- """ Maps a device to a frontend_domain.
1043
-
1044
- There are 3 flags `attached`, `automatically_attached` and `required`.
1045
- The meaning of valid combinations is as follows:
1046
- 1. (True, False, False) -> domain is running, device is manually attached
1047
- and could be manually detach any time.
1048
- 2. (True, True, False) -> domain is running, device is attached
1049
- and could be manually detach any time (see 4.),
1050
- but in the future will be auto-attached again.
1051
- 3. (True, True, True) -> domain is running, device is attached
1052
- and couldn't be detached.
1053
- 4. (False, Ture, False) -> device is assigned to domain, but not attached
1054
- because either (i) domain is halted,
1055
- device (ii) manually detached or
1056
- (iii) attach to different domain.
1057
- 5. (False, True, True) -> domain is halted, device assigned to domain
1058
- and required to start domain.
1100
+ """
1101
+ Maps a device to a frontend_domain.
1059
1102
"""
1060
1103
1061
1104
def __init__ (
@@ -1116,23 +1159,28 @@ def __lt__(self, other):
1116
1159
"is not supported" )
1117
1160
1118
1161
@property
1119
- def backend_domain (self ):
1162
+ def backend_domain (self ) -> QubesVM :
1163
+ # pylint: disable=missing-function-docstring
1120
1164
return self .virtual_device .backend_domain
1121
1165
1122
1166
@property
1123
1167
def backend_name (self ) -> str :
1168
+ # pylint: disable=missing-function-docstring
1124
1169
return self .virtual_device .backend_name
1125
1170
1126
1171
@property
1127
- def port_id (self ):
1172
+ def port_id (self ) -> str :
1173
+ # pylint: disable=missing-function-docstring
1128
1174
return self .virtual_device .port_id
1129
1175
1130
1176
@property
1131
- def devclass (self ):
1177
+ def devclass (self ) -> str :
1178
+ # pylint: disable=missing-function-docstring
1132
1179
return self .virtual_device .devclass
1133
1180
1134
1181
@property
1135
- def device_id (self ):
1182
+ def device_id (self ) -> str :
1183
+ # pylint: disable=missing-function-docstring
1136
1184
return self .virtual_device .device_id
1137
1185
1138
1186
@property
@@ -1241,7 +1289,8 @@ def serialize(self) -> bytes:
1241
1289
'frontend_domain' , self .frontend_domain .name )
1242
1290
1243
1291
for key , value in self .options .items ():
1244
- properties += b' ' + DeviceSerializer .pack_property ("_" + key , value )
1292
+ properties += b' ' + DeviceSerializer .pack_property (
1293
+ "_" + key , value )
1245
1294
1246
1295
return properties
1247
1296
@@ -1275,22 +1324,26 @@ def _deserialize(
1275
1324
1276
1325
DeviceSerializer .parse_basic_device_properties (
1277
1326
expected_device , properties )
1327
+
1328
+ expected_device = expected_device .clone (
1329
+ device_id = properties ['device_id' ])
1278
1330
# we do not need port, we need device
1279
1331
del properties ['port' ]
1280
- expected_device ._device_id = properties .get (
1281
- 'device_id' , expected_device .device_id )
1282
1332
properties .pop ('device_id' , None )
1283
1333
properties ['device' ] = expected_device
1284
1334
1285
1335
return cls (** properties )
1286
1336
1287
1337
def matches (self , device : VirtualDevice ) -> bool :
1338
+ """
1339
+ Checks if the given device matches the assignment.
1340
+ """
1288
1341
if self .devclass != device .devclass :
1289
1342
return False
1290
1343
if self .backend_domain != device .backend_domain :
1291
1344
return False
1292
- if self .port_id != '*' and self . port_id != device .port_id :
1345
+ if self .port_id not in ( '*' , device .port_id ) :
1293
1346
return False
1294
- if self .device_id != '*' and self . device_id != device .device_id :
1347
+ if self .device_id not in ( '*' , device .device_id ) :
1295
1348
return False
1296
1349
return True
0 commit comments