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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""LE GATT Security Levels characteristic (0x2BF5).
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.
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.
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"""
27from __future__ import annotations
29from enum import IntEnum
31import msgspec
33from ..context import CharacteristicContext
34from .base import BaseCharacteristic
37class LESecurityMode(IntEnum):
38 """LE security modes (BT Core Spec v6.0, Vol 3, Part C, Section 10.2)."""
40 ENCRYPTION = 1
41 DATA_SIGNING = 2
42 BROADCAST_ISOCHRONOUS = 3
45class LESecurityModeLevel(IntEnum):
46 """Combined security mode+level values for LE GATT Security Levels.
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.
52 BT Core Spec v6.0, Vol 3, Part C, Section 10.2.
53 """
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
61 # Mode 2: Data signing
62 MODE2_UNAUTH_SIGNING = 0x0201
63 MODE2_AUTH_SIGNING = 0x0202
65 # Mode 3: Broadcast isochronous
66 MODE3_NO_SECURITY = 0x0301
67 MODE3_UNAUTH_BROADCAST_CODE = 0x0302
68 MODE3_AUTH_BROADCAST_CODE = 0x0303
70 @property
71 def security_mode(self) -> LESecurityMode:
72 """The LE security mode number."""
73 return LESecurityMode(self.value >> 8)
75 @property
76 def security_level(self) -> int:
77 """The security level number within the mode."""
78 return self.value & 0xFF
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)
86class SecurityLevelRequirement(msgspec.Struct):
87 """A single security level requirement (mode, level) pair."""
89 mode: int
90 level: int
92 @property
93 def mode_level(self) -> LESecurityModeLevel:
94 """Combined mode+level enum value."""
95 return LESecurityModeLevel.from_mode_level(self.mode, self.level)
98class LEGATTSecurityLevelsCharacteristic(BaseCharacteristic[list[SecurityLevelRequirement]]):
99 """LE GATT Security Levels characteristic (0x2BF5).
101 org.bluetooth.characteristic.le_gatt_security_levels
103 Sequence of (security_mode, security_level) pairs indicating
104 the highest security requirements of the GATT server on an LE connection.
105 """
107 _characteristic_name = "LE GATT Security Levels"
108 min_length: int = 2
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)]
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