Coverage for src / bluetooth_sig / advertising / state.py: 100%

18 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Typed state classes for payload interpretation. 

2 

3State management is the caller's responsibility (connection manager, device tracker). 

4The interpreter receives state, uses it, and returns updated values. 

5The caller decides when/how to persist. 

6 

7Based on Bluetooth SIG Core Specification Supplement for advertising data patterns 

8and real-world implementations (BTHome, Xiaomi MiBeacon, RuuviTag). 

9""" 

10 

11from __future__ import annotations 

12 

13import msgspec 

14 

15 

16class EncryptionState(msgspec.Struct, kw_only=True, frozen=False): 

17 """Encryption-related state for a device. 

18 

19 Caller is responsible for persisting and updating this state. 

20 Interpreter reads current values and returns new values. 

21 

22 Attributes: 

23 bindkey: 16-byte AES-CCM key for decryption (pre-shared). 

24 bindkey_verified: Whether bindkey has successfully decrypted a payload. 

25 encryption_counter: Monotonically increasing counter for replay protection. 

26 BTHome/Xiaomi use 4-byte counter in advertisement payload. 

27 decryption_failed: Whether last decryption attempt failed. 

28 

29 """ 

30 

31 bindkey: bytes | None = None 

32 bindkey_verified: bool = False 

33 encryption_counter: int = 0 

34 decryption_failed: bool = False 

35 

36 

37class PacketState(msgspec.Struct, kw_only=True, frozen=False): 

38 """Packet tracking state for duplicate/replay detection. 

39 

40 Caller is responsible for persisting and updating this state. 

41 

42 Attributes: 

43 packet_id: Last seen packet ID (for BTHome v2 duplicate filtering). 

44 last_seen_timestamp: Timestamp of last valid advertisement (Unix epoch seconds). 

45 last_service_data_hash: Hash of last service data payload (for same-payload detection). 

46 

47 """ 

48 

49 packet_id: int | None = None 

50 last_seen_timestamp: float = 0.0 

51 last_service_data_hash: int | None = None 

52 

53 

54class DeviceAdvertisingState(msgspec.Struct, kw_only=True, frozen=False): 

55 """Complete advertising state for a device. 

56 

57 Managed by caller (connection manager, device tracker). 

58 Passed to interpreter; interpreter updates state directly. 

59 

60 Attributes: 

61 address: Device MAC address or platform identifier. 

62 encryption: Encryption-related state. 

63 packets: Packet tracking state. 

64 device_type: Detected device type from payload (e.g., "BTHome sensor"). 

65 protocol_version: Detected protocol version (e.g., "v2", "MiBeacon v5"). 

66 is_sleepy_device: Whether device uses irregular advertising intervals. 

67 

68 """ 

69 

70 address: str = "" 

71 encryption: EncryptionState = msgspec.field(default_factory=EncryptionState) 

72 packets: PacketState = msgspec.field(default_factory=PacketState) 

73 device_type: str | None = None 

74 protocol_version: str | None = None 

75 is_sleepy_device: bool = False