Skip to content

Commit c931526

Browse files
committed
Redefine DeviceCategories and improvements in MockDevices
- changed DeviceCategory listings to more closely align with desired GUI list of blockable devices - improved mocks to include better MockDevices - fix a problem in DeviceCategory definition which made some DeviceCategories single strings and some lists of string
1 parent 3eb4db8 commit c931526

3 files changed

Lines changed: 171 additions & 69 deletions

File tree

qubesadmin/device_protocol.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -641,29 +641,30 @@ class DeviceCategory(Enum):
641641
"""
642642

643643
# pylint: disable=invalid-name
644-
Other = "*******"
644+
Other = ("*******", ) # also matches all devices, if used to block attachment
645645

646-
Communication = ("u02****", "p07****") # eg. modems
646+
# The following devices are used in GUI for blocks; take note when changing
647+
648+
# modems, WiFi and Ethernet adapters
649+
Network = ("u02****", "p0703**", "p02****")
647650
Input = ("u03****", "p09****") # HID etc.
648651
Keyboard = ("u03**01", "p0900**")
649652
Mouse = ("u03**02", "p0902**")
650653
Printer = ("u07****",)
651-
Scanner = ("p0903**",)
652-
Microphone = ("m******",)
654+
Image_Input = ("p0903**", "u06****", "u0e****") # cameras and scanners
655+
653656
# Multimedia = Audio, Video, Displays etc.
654-
Multimedia = (
655-
"u06****",
656-
"u10****",
657-
"p03****",
658-
"p04****",
659-
)
660-
Audio = ("p0403**", "p0401**", "u01****")
661-
Display = ("p0300**", "p0380**")
662-
Video = ("p0400**", "u0e****")
663-
Wireless = ("ue0****", "p0d****")
664-
Bluetooth = ("ue00101", "p0d11**")
657+
Multimedia_Output = ("u10****", "p03****", "p04****")
658+
Audio = ("p0403**", "p0401**", "u01****", "m******")
659+
Microphone = ("m******",)
660+
USB_Storage = ("u08****", )
661+
Block_Storage = ("b******", )
665662
Storage = ("b******", "u08****", "p01****")
666-
Network = ("p02****",)
663+
Bluetooth = ("ue00101", "p0d11**")
664+
Smart_Card_Readers = ("u0b****", )
665+
666+
Display = ("p0300**", "p0380**") # PCI screens?
667+
Wireless = ("ue0****", "p0d****")
667668
Memory = ("p05****",)
668669
PCI_Bridge = ("p06****",)
669670
Docking_Station = ("p0a****",)
@@ -677,7 +678,7 @@ def from_str(interface_encoding: str) -> "DeviceCategory":
677678
Returns `DeviceCategory` from data encoded in string.
678679
"""
679680
result = DeviceCategory.Other
680-
if len(interface_encoding) != len(DeviceCategory.Other.value):
681+
if len(interface_encoding) != len(DeviceCategory.Other.value[0]):
681682
return result
682683
best_score = 0
683684

qubesadmin/tests/mock_app.py

Lines changed: 150 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def main():
6363
from unittest import mock
6464
from copy import deepcopy
6565

66-
from typing import List, Optional, Dict, Tuple
66+
from typing import List, Optional, Dict, Tuple, Any
6767

6868
import qubesadmin.events
6969
from qubesadmin.tests import QubesTest
@@ -120,6 +120,7 @@ def default_string(self):
120120
"debug": Property("False", "bool", True),
121121
"default_dispvm": Property("default-dvm", "vm", False),
122122
"default_user": Property("user", "str", True),
123+
"devices_denied": Property("", "str", True),
123124
"dns": Property("10.139.1.1 10.139.1.2", "str", True),
124125
"gateway": Property("", "str", True),
125126
"gateway6": Property("", "str", True),
@@ -224,6 +225,18 @@ def default_string(self):
224225
"boot-mode.name.mode1",
225226
"boot-mode.name.mode2",
226227
"service.updates-proxy-setup",
228+
"qubes-vm-update-update-if-stale",
229+
"restart-after-update",
230+
"qubes-vm-update-hide-skipped",
231+
"template-name",
232+
"last-updates-check",
233+
"last-update",
234+
"prohibit-start",
235+
"qubes-vm-update-hide-updated",
236+
"qubes-vm-update-restart-servicevms",
237+
"qubes-vm-update-restart-system",
238+
"qubes-vm-update-restart-other",
239+
"qubes-vm-update-max-concurrency",
227240
]
228241

229242
POSSIBLE_TAGS = ["whonix-updatevm", "anon-gateway", "anon-vm"]
@@ -648,72 +661,80 @@ def __init__(self, qapp):
648661
continue
649662

650663

664+
# pylint: disable=too-many-positional-arguments
651665
class MockDevice:
652666
"""helper for adding a device to a qubes test instance"""
653667

654668
def __init__(
655669
self,
656670
qapp: QubesTest,
657671
dev_class: str,
658-
description: str,
659-
dev_id: str,
672+
device_id: str,
660673
backend_vm: str,
674+
port: str,
675+
product: str,
676+
vendor: str,
661677
attached: Optional[str] = None,
678+
assigned: Optional[List[Tuple[str, str, list[Any] | None]]] = None,
662679
):
663680
"""
664681
:param qapp: QubesTest object
665682
:param dev_class: block / mic / usb
666-
:param description: device description
667-
:param dev_id: dev id (such as sda, 2-1, mic)
683+
:param device_id: device_id
684+
:param port: port
668685
:param backend_vm: name of the vm providing this device
669-
:param attached: name of the qube to which the device is attached,
670-
if any
686+
:param attached: name of the qube to which the device is
687+
currently attached,
688+
:param assigned: list of the qubes to which the device is currently
689+
assigned, tupled with mode ('ask-to-attach' or 'auto-attach')
671690
"""
672691
self.qapp = qapp
673692
self.dev_class = dev_class
674-
self.description = description
675-
self.dev_id = dev_id
693+
self.device_id = device_id
694+
self.port = port
676695
self.backend_vm = backend_vm
677696
self.attached = attached
697+
self.assigned = assigned
698+
self.product = product
699+
self.vendor = vendor
700+
701+
self.interface = self.device_id.split(":")[-1]
678702

679703
self.update_calls()
680704

681705
def device_string(self):
682-
if self.dev_class == "block":
683-
port, d_id = self.dev_id.split(":", 1)
684-
return (
685-
f"{self.dev_id} device_id='{d_id}' port_id='{port}' "
686-
f"devclass='block' backend_domain='{self.backend_vm}' "
687-
f"serial='root/test.img' manufacturer='{self.description}' "
688-
"interfaces='b******'\n"
689-
)
690-
if self.dev_class == "pci":
691-
_, d_id = self.dev_id.split(":")
692-
port = self.dev_id.replace(":", "_")
693-
return (
694-
f"{port} device_id='{d_id}' "
695-
f"port_id='{port}' devclass='pci' "
696-
f"product='{self.description}' vendor='{self.description}' "
697-
f"interfaces='p0c0500' backend_domain='{self.backend_vm}'\n"
698-
)
699-
return f"{self.dev_id} description='{self.description}'\n"
706+
return (
707+
f"{self.port} device_id='{self.device_id}' "
708+
f"port_id='{self.port}' devclass='{self.dev_class}' "
709+
f"product='{self.product}' vendor='{self.vendor}' "
710+
f"interfaces='{self.interface}' "
711+
f"backend_domain='{self.backend_vm}'\n"
712+
)
700713

701714
def attachment_string(self):
702-
if ":" in self.dev_id:
703-
port, _ = self.dev_id.split(":")
704-
elif self.dev_class == "mic":
705-
port = "mic"
706-
else:
707-
port = "00"
708715
string = (
709-
f"{self.backend_vm}+{self.dev_id} "
710-
f"port_id='{port}' devclass='{self.dev_class}' "
711-
f"backend_domain='{self.backend_vm}' mode='required' "
716+
f"{self.backend_vm}+{self.port} "
717+
f"port_id='{self.port}' devclass='{self.dev_class}' "
718+
f"backend_domain='{self.backend_vm}' mode='manual' "
712719
f"frontend_domain='{self.attached}'"
713720
)
714721
string += "\n"
715722
return string
716723

724+
def assignment_string(self, vm, mode, opts):
725+
string = (
726+
f"{self.backend_vm}+{self.port} device_id='"
727+
f"{self.device_id}' "
728+
f"port_id='{self.port}' devclass='{self.dev_class}' "
729+
f"backend_domain='{self.backend_vm}' mode='{mode}' "
730+
f"frontend_domain='{vm}'"
731+
)
732+
if opts:
733+
for opt in opts:
734+
string += f" _{opt}='True'"
735+
string += "\n"
736+
return string
737+
717738
def update_calls(self):
718739
# modify call
719740
current_response = self.qapp.expected_calls[
@@ -740,22 +761,44 @@ def update_calls(self):
740761
current_response = self.qapp.expected_calls[
741762
(
742763
self.attached,
743-
f"admin.vm.device.{self.dev_class}.Assigned",
764+
f"admin.vm.device.{self.dev_class}.Attached",
744765
None,
745766
None,
746767
)
747768
]
748769
self.qapp.expected_calls[
749770
(
750771
self.attached,
751-
f"admin.vm.device.{self.dev_class}.Assigned",
772+
f"admin.vm.device.{self.dev_class}.Attached",
752773
None,
753774
None,
754775
)
755776
] = (
756777
current_response + self.attachment_string().encode()
757778
)
758779

780+
if self.assigned:
781+
for vm, mode, opts in self.assigned:
782+
current_response = self.qapp.expected_calls[
783+
(
784+
vm,
785+
f"admin.vm.device.{self.dev_class}.Assigned",
786+
None,
787+
None,
788+
)
789+
]
790+
self.qapp.expected_calls[
791+
(
792+
vm,
793+
f"admin.vm.device.{self.dev_class}.Assigned",
794+
None,
795+
None,
796+
)
797+
] = (
798+
current_response
799+
+ self.assignment_string(vm, mode, opts).encode()
800+
)
801+
759802

760803
class QubesTestWrapper(QubesTest):
761804
def __init__(self):
@@ -987,20 +1030,78 @@ def __init__(self):
9871030
self._devices = [
9881031
MockDevice(
9891032
self,
990-
"mic",
991-
"Internal Microphone",
992-
"mic",
993-
"dom0",
1033+
dev_class="mic",
1034+
device_id="dom0:mic::m000000",
1035+
backend_vm="dom0",
1036+
port="mic",
1037+
product="Internal Mic",
1038+
vendor="ACME",
9941039
attached="test-blue",
9951040
),
9961041
# the usb stick appears as multiple devices, as they are wont to
997-
MockDevice(self, "usb", "My USB Drive", "2-1", "sys-usb"),
998-
MockDevice(self, "block", " ()", "sda::0", "sys-usb"),
999-
MockDevice(self, "block", "(USB DISK)", "sda1::1", "sys-usb"),
1000-
MockDevice(self, "usb", "Internal Camera", "2-10", "sys-usb"),
1001-
MockDevice(self, "pci", "Host bridge", "00:00.0", "dom0"),
1002-
MockDevice(self, "pci", "PCI Bridge", "00:02.2", "dom0"),
1003-
MockDevice(self, "pci", "USB Controller", "00:03.2", "dom0"),
1042+
MockDevice(
1043+
self,
1044+
dev_class="usb",
1045+
device_id="1d6b:0104:CAFEBABE:u030101u300000",
1046+
backend_vm="sys-usb",
1047+
port="2-1",
1048+
product="My USB Drive",
1049+
vendor="SCP Foundation",
1050+
),
1051+
MockDevice(
1052+
self,
1053+
dev_class="block",
1054+
device_id="1d6b:0104:CAFEBABE:b123456",
1055+
backend_vm="sys-usb",
1056+
port="sda",
1057+
product="(USB)",
1058+
vendor="SCP Foundation",
1059+
),
1060+
MockDevice(
1061+
self,
1062+
dev_class="block",
1063+
device_id="1d6b:0104:CAFEBABE:b123456",
1064+
backend_vm="sys-usb",
1065+
port="sda",
1066+
product="()",
1067+
vendor="SCP Foundation",
1068+
),
1069+
MockDevice(
1070+
self,
1071+
dev_class="usb",
1072+
device_id="04f2:b684:01.00.00:u0e0101u0e0201",
1073+
backend_vm="sys-usb",
1074+
port="2-7",
1075+
product="Internal Camera",
1076+
vendor="Saruman Industries",
1077+
),
1078+
MockDevice(
1079+
self,
1080+
dev_class="pci",
1081+
device_id="0x8086:0x4621::p060000",
1082+
backend_vm="dom0",
1083+
port="00_00.0",
1084+
product="PCI Bridge",
1085+
vendor="Unnamed Industries",
1086+
),
1087+
MockDevice(
1088+
self,
1089+
dev_class="pci",
1090+
device_id="0x8086:0x51e9::p0c8000",
1091+
backend_vm="dom0",
1092+
port="00_02.0",
1093+
product="I2C Controller",
1094+
vendor="Unnamed Industries",
1095+
),
1096+
MockDevice(
1097+
self,
1098+
dev_class="pci",
1099+
device_id="0x8086:0x461e::p0c0330",
1100+
backend_vm="dom0",
1101+
port="00_03.0",
1102+
product="I2C Controller",
1103+
vendor="Unnamed Industries",
1104+
),
10041105
]
10051106

10061107
self.update_vm_calls()

qubesadmin/tests/tools/qvm_device.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def test_000_list_all(self):
8383
['testclass', 'list'], app=self.app)
8484
self.assertEqual(
8585
[x.rstrip() for x in buf.getvalue().splitlines()],
86-
['test-vm1:dev1 Audio: itl test-device',
86+
['test-vm1:dev1 Audio_Output: itl test-device',
8787
'test-vm2:dev2 ?******: ? ` test-device']
8888
)
8989

@@ -158,7 +158,7 @@ def test_002_list_attach(self):
158158
['testclass', 'list', 'test-vm3'], app=self.app)
159159
self.assertEqual(
160160
buf.getvalue(),
161-
'test-vm1:dev1 Audio: itl test-device '
161+
'test-vm1:dev1 Audio_Output: itl test-device '
162162
'test-vm3 (attached)\n'
163163
)
164164

@@ -611,7 +611,7 @@ def test_060_device_info(self):
611611
qubesadmin.tools.qvm_device.main(
612612
['testclass', 'info', 'test-vm1:dev1'],
613613
app=self.app)
614-
self.assertIn('Audio: itl test-device\n'
614+
self.assertIn('Audio_Output: itl test-device\n'
615615
'device ID: dead:beef:babe:u012345',
616616
buf.getvalue())
617617
self.assertAllCalled()

0 commit comments

Comments
 (0)