Coverage for src / bluetooth_sig / gatt / characteristics / le_gatt_security_levels.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""LE GATT Security Levels characteristic (0x2BF5). 

2 

3BT Core Spec v6.0, Vol 3, Part C, Section 12.7: 

4 The Attribute Value is a sequence of Security Level Requirements, 

5 each with the type uint8[2]. Each Security Level Requirement consists 

6 of a Security Mode field followed by a Security Level field. 

7 

8 The Security Mode and Security Level shall be expressed as the same 

9 number as used in their definitions; e.g., mode 1 is represented as 

10 0x01 and level 4 is represented as 0x04. 

11 

12Security modes and levels defined in Section 10.2: 

13 Mode 1 (encryption-based): 

14 Level 1: No security (no authentication, no encryption) 

15 Level 2: Unauthenticated pairing with encryption 

16 Level 3: Authenticated pairing with encryption 

17 Level 4: Authenticated LE Secure Connections pairing with 128-bit encryption 

18 Mode 2 (data signing): 

19 Level 1: Unauthenticated pairing with data signing 

20 Level 2: Authenticated pairing with data signing 

21 Mode 3 (broadcast isochronous): 

22 Level 1: No security (no authentication, no encryption) 

23 Level 2: Use of unauthenticated Broadcast_Code 

24 Level 3: Use of authenticated Broadcast_Code 

25""" 

26 

27from __future__ import annotations 

28 

29from enum import IntEnum 

30 

31import msgspec 

32 

33from ..context import CharacteristicContext 

34from .base import BaseCharacteristic 

35 

36 

37class LESecurityMode(IntEnum): 

38 """LE security modes (BT Core Spec v6.0, Vol 3, Part C, Section 10.2).""" 

39 

40 ENCRYPTION = 1 

41 DATA_SIGNING = 2 

42 BROADCAST_ISOCHRONOUS = 3 

43 

44 

45class LESecurityModeLevel(IntEnum): 

46 """Combined security mode+level values for LE GATT Security Levels. 

47 

48 Each value encodes (mode << 8 | level) for unique identification. 

49 The raw wire mode/level bytes can be recovered via the mode and level 

50 properties. 

51 

52 BT Core Spec v6.0, Vol 3, Part C, Section 10.2. 

53 """ 

54 

55 # Mode 1: Encryption-based security 

56 MODE1_NO_SECURITY = 0x0101 

57 MODE1_UNAUTH_ENCRYPTION = 0x0102 

58 MODE1_AUTH_ENCRYPTION = 0x0103 

59 MODE1_AUTH_SC_ENCRYPTION = 0x0104 

60 

61 # Mode 2: Data signing 

62 MODE2_UNAUTH_SIGNING = 0x0201 

63 MODE2_AUTH_SIGNING = 0x0202 

64 

65 # Mode 3: Broadcast isochronous 

66 MODE3_NO_SECURITY = 0x0301 

67 MODE3_UNAUTH_BROADCAST_CODE = 0x0302 

68 MODE3_AUTH_BROADCAST_CODE = 0x0303 

69 

70 @property 

71 def security_mode(self) -> LESecurityMode: 

72 """The LE security mode number.""" 

73 return LESecurityMode(self.value >> 8) 

74 

75 @property 

76 def security_level(self) -> int: 

77 """The security level number within the mode.""" 

78 return self.value & 0xFF 

79 

80 @classmethod 

81 def from_mode_level(cls, mode: int, level: int) -> LESecurityModeLevel: 

82 """Construct from raw mode and level bytes.""" 

83 return cls((mode << 8) | level) 

84 

85 

86class SecurityLevelRequirement(msgspec.Struct): 

87 """A single security level requirement (mode, level) pair.""" 

88 

89 mode: int 

90 level: int 

91 

92 @property 

93 def mode_level(self) -> LESecurityModeLevel: 

94 """Combined mode+level enum value.""" 

95 return LESecurityModeLevel.from_mode_level(self.mode, self.level) 

96 

97 

98class LEGATTSecurityLevelsCharacteristic(BaseCharacteristic[list[SecurityLevelRequirement]]): 

99 """LE GATT Security Levels characteristic (0x2BF5). 

100 

101 org.bluetooth.characteristic.le_gatt_security_levels 

102 

103 Sequence of (security_mode, security_level) pairs indicating 

104 the highest security requirements of the GATT server on an LE connection. 

105 """ 

106 

107 _characteristic_name = "LE GATT Security Levels" 

108 min_length: int = 2 

109 

110 def _decode_value( 

111 self, 

112 data: bytearray, 

113 ctx: CharacteristicContext | None = None, 

114 *, 

115 validate: bool = True, 

116 ) -> list[SecurityLevelRequirement]: 

117 if len(data) % 2 != 0: 

118 msg = f"Data length must be even (pairs of uint8), got {len(data)}" 

119 raise ValueError(msg) 

120 return [SecurityLevelRequirement(mode=data[i], level=data[i + 1]) for i in range(0, len(data), 2)] 

121 

122 def _encode_value(self, value: list[SecurityLevelRequirement]) -> bytearray: 

123 result = bytearray() 

124 for req in value: 

125 result.append(req.mode) 

126 result.append(req.level) 

127 return result