Coverage for tropicsquare / config / uap_base.py: 94%
105 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 21:24 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 21:24 +0000
1"""Base classes and helpers for User Access Policy (UAP) configuration"""
3from tropicsquare.config.base import BaseConfig
6class UapPermissionField:
7 """Represents an 8-bit UAP permission field.
9 Each field contains permission bits for 4 pairing key slots:
11 - Bit 0: Pairing Key slot 0 has access
12 - Bit 1: Pairing Key slot 1 has access
13 - Bit 2: Pairing Key slot 2 has access
14 - Bit 3: Pairing Key slot 3 has access
15 - Bits 4-7: Reserved
16 """
18 def __init__(self, value: int = 0xFF) -> None:
19 """Initialize permission field.
21 :param value: 8-bit permission value (default: 0xFF = all slots have access)
22 """
23 self._value = value & 0xFF
25 def get_slot_permission(self, slot: int) -> bool:
26 """Check if pairing key slot has access.
28 :param slot: Slot number (0-3)
30 :returns: True if slot has access
31 """
32 if not 0 <= slot <= 3:
33 raise ValueError("Slot must be 0-3, got {}".format(slot))
34 return bool((self._value >> slot) & 1)
36 def set_slot_permission(self, slot: int, has_access: bool) -> None:
37 """Set permission for pairing key slot.
39 :param slot: Slot number (0-3)
40 :param has_access: True to grant access, False to deny
41 """
42 if not 0 <= slot <= 3:
43 raise ValueError("Slot must be 0-3, got {}".format(slot))
44 if has_access:
45 self._value |= (1 << slot)
46 else:
47 self._value &= ~(1 << slot)
49 @property
50 def pkey_slot_0(self) -> bool:
51 """Pairing Key slot 0 has access."""
52 return self.get_slot_permission(0)
54 @pkey_slot_0.setter
55 def pkey_slot_0(self, value: bool) -> None:
56 self.set_slot_permission(0, value)
58 @property
59 def pkey_slot_1(self) -> bool:
60 """Pairing Key slot 1 has access."""
61 return self.get_slot_permission(1)
63 @pkey_slot_1.setter
64 def pkey_slot_1(self, value: bool) -> None:
65 self.set_slot_permission(1, value)
67 @property
68 def pkey_slot_2(self) -> bool:
69 """Pairing Key slot 2 has access."""
70 return self.get_slot_permission(2)
72 @pkey_slot_2.setter
73 def pkey_slot_2(self, value: bool) -> None:
74 self.set_slot_permission(2, value)
76 @property
77 def pkey_slot_3(self) -> bool:
78 """Pairing Key slot 3 has access."""
79 return self.get_slot_permission(3)
81 @pkey_slot_3.setter
82 def pkey_slot_3(self, value: bool) -> None:
83 self.set_slot_permission(3, value)
85 @property
86 def value(self) -> int:
87 """Raw 8-bit value."""
88 return self._value
90 @value.setter
91 def value(self, val: int) -> None:
92 self._value = val & 0xFF
94 def to_dict(self) -> dict:
95 """Export as dictionary."""
96 return {
97 'pkey_slot_0': self.pkey_slot_0,
98 'pkey_slot_1': self.pkey_slot_1,
99 'pkey_slot_2': self.pkey_slot_2,
100 'pkey_slot_3': self.pkey_slot_3
101 }
103 def __str__(self) -> str:
104 """Format as table cells: x | x | | x
106 No leading/trailing pipes - will be added by parent class.
107 Returns 4 cells separated by ' | '.
108 """
109 parts = []
110 for i in range(4):
111 if self.get_slot_permission(i):
112 parts.append("x")
113 else:
114 parts.append(" ") # 1 space for alignment
115 return " | ".join(parts)
118class UapMultiSlotConfig(BaseConfig):
119 """Base class for UAP configs with multiple slots.
121 Used for configs that have 4 slots, each with 8-bit permission field.
122 """
124 def _get_slot_field(self, slot_pos: int) -> UapPermissionField:
125 """Get 8-bit permission field at slot position.
127 :param slot_pos: Bit position of slot (0, 8, 16, or 24)
129 :returns: Permission field at the specified slot position
130 :rtype: UapPermissionField
131 """
132 field_value = (self._value >> slot_pos) & 0xFF
133 return UapPermissionField(field_value)
135 def _set_slot_field(self, slot_pos: int, field: UapPermissionField) -> None:
136 """Set 8-bit permission field at slot position.
138 :param slot_pos: Bit position of slot (0, 8, 16, or 24)
139 :param field: UapPermissionField object
140 """
141 field_value = field.value
143 # Clear existing field and set new value
144 mask = 0xFF << slot_pos
145 self._value = (self._value & ~mask) | (field_value << slot_pos)
147 def __str__(self) -> str:
148 """Table row: ClassName | slot_0 || slot_1 || slot_2 || slot_3 |
150 Uses || to visually separate 4 different slot permission fields.
151 """
152 s0 = str(self._get_slot_field(0))
153 s1 = str(self._get_slot_field(8))
154 s2 = str(self._get_slot_field(16))
155 s3 = str(self._get_slot_field(24))
156 return "{:26s} | {} || {} || {} || {} |".format(
157 self.__class__.__name__,
158 s0, s1, s2, s3
159 )
162class UapSingleFieldConfig(BaseConfig):
163 """Base class for UAP configs with single 8-bit permission field."""
165 def __init__(self, value: int = 0xFFFFFFFF) -> None:
166 """Initialize with default all-access value."""
167 super().__init__(value)
169 @property
170 def permissions(self) -> UapPermissionField:
171 """Get permission field (8 bits at position 0)."""
172 field_value = self._value & 0xFF
173 return UapPermissionField(field_value)
175 @permissions.setter
176 def permissions(self, field: UapPermissionField) -> None:
177 """Set permission field."""
178 self._value = (self._value & ~0xFF) | field.value
180 def to_dict(self) -> dict:
181 """Export as dictionary."""
182 return {
183 'permissions': self.permissions.to_dict()
184 }
186 def __str__(self) -> str:
187 """Table row: ClassName | permissions cells |"""
188 perm_str = str(self.permissions)
189 return "{:26s} | {} |".format(
190 self.__class__.__name__,
191 perm_str
192 )
195class UapDualFieldConfig(BaseConfig):
196 """Base class for UAP configs with two 8-bit permission fields (CFG and FUNC)."""
198 def __init__(self, value: int = 0xFFFFFFFF) -> None:
199 """Initialize with default all-access value."""
200 super().__init__(value)
202 @property
203 def cfg_permissions(self) -> UapPermissionField:
204 """Get CFG permission field (8 bits at position 0)."""
205 field_value = self._value & 0xFF
206 return UapPermissionField(field_value)
208 @cfg_permissions.setter
209 def cfg_permissions(self, field: UapPermissionField) -> None:
210 """Set CFG permission field."""
211 self._value = (self._value & ~0xFF) | field.value
213 @property
214 def func_permissions(self) -> UapPermissionField:
215 """Get FUNC permission field (8 bits at position 8)."""
216 field_value = (self._value >> 8) & 0xFF
217 return UapPermissionField(field_value)
219 @func_permissions.setter
220 def func_permissions(self, field: UapPermissionField) -> None:
221 """Set FUNC permission field."""
222 self._value = (self._value & ~0xFF00) | (field.value << 8)
224 def to_dict(self) -> dict:
225 """Export as dictionary."""
226 return {
227 'cfg_permissions': self.cfg_permissions.to_dict(),
228 'func_permissions': self.func_permissions.to_dict()
229 }
231 def __str__(self) -> str:
232 """Table row: ClassName | cfg cells || func cells |
234 Uses || to visually separate cfg and func permission fields.
235 """
236 cfg_str = str(self.cfg_permissions)
237 func_str = str(self.func_permissions)
238 return "{:26s} | {} || {} |".format(
239 self.__class__.__name__,
240 cfg_str,
241 func_str
242 )