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

1"""Base classes and helpers for User Access Policy (UAP) configuration""" 

2 

3from tropicsquare.config.base import BaseConfig 

4 

5 

6class UapPermissionField: 

7 """Represents an 8-bit UAP permission field. 

8 

9 Each field contains permission bits for 4 pairing key slots: 

10 

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 """ 

17 

18 def __init__(self, value: int = 0xFF) -> None: 

19 """Initialize permission field. 

20 

21 :param value: 8-bit permission value (default: 0xFF = all slots have access) 

22 """ 

23 self._value = value & 0xFF 

24 

25 def get_slot_permission(self, slot: int) -> bool: 

26 """Check if pairing key slot has access. 

27 

28 :param slot: Slot number (0-3) 

29 

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) 

35 

36 def set_slot_permission(self, slot: int, has_access: bool) -> None: 

37 """Set permission for pairing key slot. 

38 

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) 

48 

49 @property 

50 def pkey_slot_0(self) -> bool: 

51 """Pairing Key slot 0 has access.""" 

52 return self.get_slot_permission(0) 

53 

54 @pkey_slot_0.setter 

55 def pkey_slot_0(self, value: bool) -> None: 

56 self.set_slot_permission(0, value) 

57 

58 @property 

59 def pkey_slot_1(self) -> bool: 

60 """Pairing Key slot 1 has access.""" 

61 return self.get_slot_permission(1) 

62 

63 @pkey_slot_1.setter 

64 def pkey_slot_1(self, value: bool) -> None: 

65 self.set_slot_permission(1, value) 

66 

67 @property 

68 def pkey_slot_2(self) -> bool: 

69 """Pairing Key slot 2 has access.""" 

70 return self.get_slot_permission(2) 

71 

72 @pkey_slot_2.setter 

73 def pkey_slot_2(self, value: bool) -> None: 

74 self.set_slot_permission(2, value) 

75 

76 @property 

77 def pkey_slot_3(self) -> bool: 

78 """Pairing Key slot 3 has access.""" 

79 return self.get_slot_permission(3) 

80 

81 @pkey_slot_3.setter 

82 def pkey_slot_3(self, value: bool) -> None: 

83 self.set_slot_permission(3, value) 

84 

85 @property 

86 def value(self) -> int: 

87 """Raw 8-bit value.""" 

88 return self._value 

89 

90 @value.setter 

91 def value(self, val: int) -> None: 

92 self._value = val & 0xFF 

93 

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 } 

102 

103 def __str__(self) -> str: 

104 """Format as table cells: x | x | | x 

105 

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) 

116 

117 

118class UapMultiSlotConfig(BaseConfig): 

119 """Base class for UAP configs with multiple slots. 

120 

121 Used for configs that have 4 slots, each with 8-bit permission field. 

122 """ 

123 

124 def _get_slot_field(self, slot_pos: int) -> UapPermissionField: 

125 """Get 8-bit permission field at slot position. 

126 

127 :param slot_pos: Bit position of slot (0, 8, 16, or 24) 

128 

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) 

134 

135 def _set_slot_field(self, slot_pos: int, field: UapPermissionField) -> None: 

136 """Set 8-bit permission field at slot position. 

137 

138 :param slot_pos: Bit position of slot (0, 8, 16, or 24) 

139 :param field: UapPermissionField object 

140 """ 

141 field_value = field.value 

142 

143 # Clear existing field and set new value 

144 mask = 0xFF << slot_pos 

145 self._value = (self._value & ~mask) | (field_value << slot_pos) 

146 

147 def __str__(self) -> str: 

148 """Table row: ClassName | slot_0 || slot_1 || slot_2 || slot_3 | 

149 

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 ) 

160 

161 

162class UapSingleFieldConfig(BaseConfig): 

163 """Base class for UAP configs with single 8-bit permission field.""" 

164 

165 def __init__(self, value: int = 0xFFFFFFFF) -> None: 

166 """Initialize with default all-access value.""" 

167 super().__init__(value) 

168 

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) 

174 

175 @permissions.setter 

176 def permissions(self, field: UapPermissionField) -> None: 

177 """Set permission field.""" 

178 self._value = (self._value & ~0xFF) | field.value 

179 

180 def to_dict(self) -> dict: 

181 """Export as dictionary.""" 

182 return { 

183 'permissions': self.permissions.to_dict() 

184 } 

185 

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 ) 

193 

194 

195class UapDualFieldConfig(BaseConfig): 

196 """Base class for UAP configs with two 8-bit permission fields (CFG and FUNC).""" 

197 

198 def __init__(self, value: int = 0xFFFFFFFF) -> None: 

199 """Initialize with default all-access value.""" 

200 super().__init__(value) 

201 

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) 

207 

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 

212 

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) 

218 

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) 

223 

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 } 

230 

231 def __str__(self) -> str: 

232 """Table row: ClassName | cfg cells || func cells | 

233 

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 )