fips_core/transport/ble/
bootstrap.rs1use 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}