Skip to content

Commit 2a6841d

Browse files
committed
q-dev: introduce AnyPort
1 parent f602a3d commit 2a6841d

File tree

1 file changed

+52
-32
lines changed

1 file changed

+52
-32
lines changed

qubesadmin/device_protocol.py

+52-32
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,14 @@ def parse_basic_device_properties(
205205
properties['port'] = expected
206206

207207
@staticmethod
208-
def serialize_str(value: str):
208+
def serialize_str(value: str) -> str:
209209
"""
210210
Serialize python string to ensure consistency.
211211
"""
212212
return "'" + str(value).replace("'", r"\'") + "'"
213213

214214
@staticmethod
215-
def deserialize_str(value: str):
215+
def deserialize_str(value: str) -> str:
216216
"""
217217
Deserialize python string to ensure consistency.
218218
"""
@@ -255,7 +255,11 @@ class Port:
255255
port_id (str): A unique (in backend domain) identifier for the port.
256256
devclass (str): The class of the port (e.g., 'usb', 'pci').
257257
"""
258-
def __init__(self, backend_domain, port_id, devclass):
258+
def __init__(self,
259+
backend_domain: Optional[QubesVM],
260+
port_id: str,
261+
devclass: str
262+
):
259263
self.__backend_domain = backend_domain
260264
self.__port_id = port_id
261265
self.__devclass = devclass
@@ -288,7 +292,7 @@ def __str__(self):
288292
@property
289293
def backend_name(self) -> str:
290294
# pylint: disable=missing-function-docstring
291-
if self.backend_domain not in (None, "*"):
295+
if self.backend_domain is not None:
292296
return self.backend_domain.name
293297
return "*"
294298

@@ -359,12 +363,22 @@ def devclass(self) -> str:
359363
return self.__devclass
360364
return "peripheral"
361365

362-
363366
@property
364367
def has_devclass(self):
365368
return self.__devclass is not None
366369

367370

371+
class AnyPort(Port):
372+
def __init__(self, devclass: str):
373+
super().__init__(None, "*", devclass)
374+
375+
def __repr__(self):
376+
return "*"
377+
378+
def __str__(self):
379+
return "*"
380+
381+
368382
class VirtualDevice:
369383
"""
370384
Class of a device connected to *port*.
@@ -378,7 +392,7 @@ def __init__(
378392
port: Optional[Port] = None,
379393
device_id: Optional[str] = None,
380394
):
381-
assert port is not None or device_id is not None
395+
assert not isinstance(port, AnyPort) or device_id is not None
382396
self.port: Optional[Port] = port
383397
self._device_id = device_id
384398

@@ -394,19 +408,26 @@ def clone(self, **kwargs) -> 'VirtualDevice':
394408
return VirtualDevice(**attr)
395409

396410
@property
397-
def port(self) -> Union[Port, str]:
411+
def port(self) -> Port:
398412
# pylint: disable=missing-function-docstring
399413
return self._port
400414

401415
@port.setter
402416
def port(self, value: Union[Port, str, None]):
403417
# pylint: disable=missing-function-docstring
404-
self._port = value if value is not None else '*'
418+
if isinstance(value, Port):
419+
self._port = value
420+
return
421+
if isinstance(value, str) and value != '*':
422+
raise ValueError("Unsupported value for port")
423+
if self.device_id == '*':
424+
raise ValueError("Cannot set port to '*' if device_is is '*'")
425+
self._port = AnyPort(self.devclass)
405426

406427
@property
407428
def device_id(self) -> str:
408429
# pylint: disable=missing-function-docstring
409-
if self._device_id is not None:
430+
if self.is_device_id_set:
410431
return self._device_id
411432
return '*'
412433

@@ -418,34 +439,26 @@ def is_device_id_set(self) -> bool:
418439
return self._device_id is not None
419440

420441
@property
421-
def backend_domain(self) -> Union[QubesVM, str]:
442+
def backend_domain(self) -> Optional[QubesVM]:
422443
# pylint: disable=missing-function-docstring
423-
if self.port != '*' and self.port.backend_domain is not None:
424-
return self.port.backend_domain
425-
return '*'
444+
return self.port.backend_domain
426445

427446
@property
428447
def backend_name(self) -> str:
429448
"""
430449
Return backend domain name if any or `*`.
431450
"""
432-
if self.port != '*':
433-
return self.port.backend_name
434-
return '*'
451+
return self.port.backend_name
435452

436453
@property
437454
def port_id(self) -> str:
438455
# pylint: disable=missing-function-docstring
439-
if self.port != '*' and self.port.port_id is not None:
440-
return self.port.port_id
441-
return '*'
456+
return self.port.port_id
442457

443458
@property
444459
def devclass(self) -> str:
445460
# pylint: disable=missing-function-docstring
446-
if self.port != '*' and self.port.devclass is not None:
447-
return self.port.devclass
448-
return '*'
461+
return self.port.devclass
449462

450463
@property
451464
def description(self) -> str:
@@ -483,9 +496,11 @@ def __lt__(self, other):
483496
4. *:*
484497
"""
485498
if isinstance(other, (VirtualDevice, DeviceAssignment)):
486-
if self.port == '*' and other.port != '*':
499+
if (isinstance(self.port, AnyPort)
500+
and not isinstance(other.port, AnyPort)):
487501
return True
488-
if self.port != '*' and other.port == '*':
502+
if (not isinstance(self.port, AnyPort)
503+
and isinstance(other.port, AnyPort)):
489504
return False
490505
reprs = {self: [self.port], other: [other.port]}
491506
for obj, obj_repr in reprs.items():
@@ -509,10 +524,10 @@ def __str__(self):
509524
def from_qarg(
510525
cls,
511526
representation: str,
512-
devclass,
527+
devclass: Optional[str],
513528
domains,
514-
blind=False,
515-
backend=None,
529+
blind: bool = False,
530+
backend: Optional[QubesVM] = None,
516531
) -> 'VirtualDevice':
517532
"""
518533
Parse qrexec argument <back_vm>+<port_id>:<device_id> to get device info
@@ -528,8 +543,12 @@ def from_qarg(
528543

529544
@classmethod
530545
def from_str(
531-
cls, representation: str, devclass: Optional[str], domains,
532-
blind=False, backend=None
546+
cls,
547+
representation: str,
548+
devclass: Optional[str],
549+
domains,
550+
blind: bool = False,
551+
backend: Optional[QubesVM] = None,
533552
) -> 'VirtualDevice':
534553
"""
535554
Parse string <back_vm>+<port_id>:<device_id> to get device info
@@ -549,7 +568,7 @@ def _parse(
549568
representation: str,
550569
devclass: Optional[str],
551570
get_domain: Callable,
552-
backend,
571+
backend: Optional[QubesVM],
553572
sep: str
554573
) -> 'VirtualDevice':
555574
"""
@@ -942,7 +961,8 @@ def subdevices(self) -> List[VirtualDevice]:
942961
If the device has subdevices (e.g., partitions of a USB stick),
943962
the subdevices id should be here.
944963
"""
945-
return [dev for dev in self.backend_domain.devices[self.devclass]
964+
return [dev for devclass in self.backend_domain.devices.keys()
965+
for dev in self.backend_domain.devices[devclass]
946966
if dev.parent_device.port.port_id == self.port_id]
947967

948968
@property
@@ -956,7 +976,7 @@ def serialize(self) -> bytes:
956976
"""
957977
Serialize an object to be transmitted via Qubes API.
958978
"""
959-
properties = VirtualDevice.serialize(self)
979+
properties = super().serialize()
960980
# 'attachment', 'interfaces', 'data', 'parent_device'
961981
# are not string, so they need special treatment
962982
default = DeviceInfo(self.port)

0 commit comments

Comments
 (0)