Skip to main content

fips_core/transport/ble/
bootstrap.rs

1//! GATT bootstrap record for FIPS BLE v2.
2
3use thiserror::Error;
4
5pub const FIPS_BLE_V2_SERVICE_UUID: u128 = 0x9c90_b792_2cc5_42c0_9f87_c9cc_4064_8f4c;
6pub const FIPS_BLE_V2_BOOTSTRAP_CHARACTERISTIC_UUID: u128 =
7    0x9c90_b793_2cc5_42c0_9f87_c9cc_4064_8f4c;
8pub const BOOTSTRAP_LEN: usize = 8;
9const MAGIC: [u8; 2] = *b"FB";
10const VERSION: u8 = 2;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct BleBootstrap {
14    pub capabilities: u8,
15    pub psm: u16,
16    pub max_packet: u16,
17}
18
19impl BleBootstrap {
20    pub fn new(psm: u16, max_packet: u16) -> Result<Self, BleBootstrapError> {
21        if psm == 0 {
22            return Err(BleBootstrapError::InvalidPsm);
23        }
24        if max_packet == 0 {
25            return Err(BleBootstrapError::InvalidMaxPacket);
26        }
27        Ok(Self {
28            capabilities: 0,
29            psm,
30            max_packet,
31        })
32    }
33
34    pub fn encode(self) -> [u8; BOOTSTRAP_LEN] {
35        let psm = self.psm.to_be_bytes();
36        let max_packet = self.max_packet.to_be_bytes();
37        [
38            MAGIC[0],
39            MAGIC[1],
40            VERSION,
41            self.capabilities,
42            psm[0],
43            psm[1],
44            max_packet[0],
45            max_packet[1],
46        ]
47    }
48
49    pub fn decode(bytes: &[u8]) -> Result<Self, BleBootstrapError> {
50        if bytes.len() != BOOTSTRAP_LEN {
51            return Err(BleBootstrapError::InvalidLength(bytes.len()));
52        }
53        if bytes[..2] != MAGIC {
54            return Err(BleBootstrapError::InvalidMagic);
55        }
56        if bytes[2] != VERSION {
57            return Err(BleBootstrapError::UnsupportedVersion(bytes[2]));
58        }
59        if bytes[3] != 0 {
60            return Err(BleBootstrapError::UnsupportedCapabilities(bytes[3]));
61        }
62        let record = Self {
63            capabilities: bytes[3],
64            psm: u16::from_be_bytes([bytes[4], bytes[5]]),
65            max_packet: u16::from_be_bytes([bytes[6], bytes[7]]),
66        };
67        Self::new(record.psm, record.max_packet)
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Error)]
72pub enum BleBootstrapError {
73    #[error("BLE bootstrap must contain exactly {BOOTSTRAP_LEN} bytes, got {0}")]
74    InvalidLength(usize),
75    #[error("invalid BLE bootstrap magic")]
76    InvalidMagic,
77    #[error("unsupported BLE bootstrap version {0}")]
78    UnsupportedVersion(u8),
79    #[error("unsupported BLE bootstrap capabilities 0x{0:02x}")]
80    UnsupportedCapabilities(u8),
81    #[error("BLE bootstrap PSM must not be zero")]
82    InvalidPsm,
83    #[error("BLE bootstrap maximum packet must not be zero")]
84    InvalidMaxPacket,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn bootstrap_roundtrip() {
93        let record = BleBootstrap::new(0x0085, 2048).unwrap();
94        assert_eq!(BleBootstrap::decode(&record.encode()), Ok(record));
95    }
96
97    #[test]
98    fn rejects_unknown_or_invalid_bootstrap_values() {
99        let valid = BleBootstrap::new(0x0085, 2048).unwrap().encode();
100
101        let mut wrong_version = valid;
102        wrong_version[2] = 3;
103        assert_eq!(
104            BleBootstrap::decode(&wrong_version),
105            Err(BleBootstrapError::UnsupportedVersion(3))
106        );
107
108        let mut unknown_capability = valid;
109        unknown_capability[3] = 1;
110        assert_eq!(
111            BleBootstrap::decode(&unknown_capability),
112            Err(BleBootstrapError::UnsupportedCapabilities(1))
113        );
114
115        assert_eq!(
116            BleBootstrap::new(0, 2048),
117            Err(BleBootstrapError::InvalidPsm)
118        );
119        assert_eq!(
120            BleBootstrap::new(0x0085, 0),
121            Err(BleBootstrapError::InvalidMaxPacket)
122        );
123    }
124}