Skip to main content

fips_core/transport/ble/
framing.rs

1//! FIPS BLE v2 packet framing over byte-stream L2CAP channels.
2
3use super::{addr::BleAddr, io::BleStream};
4use crate::transport::TransportError;
5use std::collections::VecDeque;
6use thiserror::Error;
7use tokio::sync::Mutex;
8
9pub const BLE_V2_MAGIC: [u8; 2] = *b"FB";
10pub const BLE_V2_VERSION: u8 = 2;
11pub const BLE_V2_HEADER_LEN: usize = 6;
12
13#[derive(Debug, Clone, PartialEq, Eq, Error)]
14pub enum BleFrameError {
15    #[error("BLE frame payload must not be empty")]
16    EmptyPayload,
17    #[error("BLE frame payload {payload_len} exceeds configured maximum {max_payload_len}")]
18    Oversized {
19        payload_len: usize,
20        max_payload_len: usize,
21    },
22    #[error("invalid BLE frame magic")]
23    InvalidMagic,
24    #[error("unsupported BLE frame version {0}")]
25    UnsupportedVersion(u8),
26    #[error("unsupported BLE frame flags 0x{0:02x}")]
27    UnsupportedFlags(u8),
28}
29
30pub fn encode_frame(payload: &[u8], max_payload_len: usize) -> Result<Vec<u8>, BleFrameError> {
31    if payload.is_empty() {
32        return Err(BleFrameError::EmptyPayload);
33    }
34    let effective_max = max_payload_len.min(u16::MAX as usize);
35    if payload.len() > effective_max {
36        return Err(BleFrameError::Oversized {
37            payload_len: payload.len(),
38            max_payload_len: effective_max,
39        });
40    }
41
42    let payload_len = payload.len() as u16;
43    let mut encoded = Vec::with_capacity(BLE_V2_HEADER_LEN + payload.len());
44    encoded.extend_from_slice(&BLE_V2_MAGIC);
45    encoded.push(BLE_V2_VERSION);
46    encoded.push(0);
47    encoded.extend_from_slice(&payload_len.to_be_bytes());
48    encoded.extend_from_slice(payload);
49    Ok(encoded)
50}
51
52#[derive(Debug)]
53pub struct BleFrameDecoder {
54    max_payload_len: usize,
55    buffered: Vec<u8>,
56}
57
58impl BleFrameDecoder {
59    pub fn new(max_payload_len: usize) -> Self {
60        Self {
61            max_payload_len: max_payload_len.min(u16::MAX as usize),
62            buffered: Vec::new(),
63        }
64    }
65
66    pub fn push(&mut self, mut bytes: &[u8]) -> Result<Vec<Vec<u8>>, BleFrameError> {
67        let mut packets = Vec::new();
68        while !bytes.is_empty() {
69            if self.buffered.len() < BLE_V2_HEADER_LEN {
70                let needed = BLE_V2_HEADER_LEN - self.buffered.len();
71                let take = needed.min(bytes.len());
72                self.buffered.extend_from_slice(&bytes[..take]);
73                bytes = &bytes[take..];
74                if self.buffered.len() < BLE_V2_HEADER_LEN {
75                    break;
76                }
77            }
78
79            let payload_len = match validate_header(&self.buffered, self.max_payload_len) {
80                Ok(payload_len) => payload_len,
81                Err(error) => {
82                    self.buffered.clear();
83                    return Err(error);
84                }
85            };
86            let frame_len = BLE_V2_HEADER_LEN + payload_len;
87            let needed = frame_len - self.buffered.len();
88            let take = needed.min(bytes.len());
89            self.buffered.extend_from_slice(&bytes[..take]);
90            bytes = &bytes[take..];
91
92            if self.buffered.len() == frame_len {
93                packets.push(self.buffered[BLE_V2_HEADER_LEN..].to_vec());
94                self.buffered.clear();
95            }
96        }
97        Ok(packets)
98    }
99
100    pub(crate) fn buffered_len(&self) -> usize {
101        self.buffered.len()
102    }
103}
104
105fn validate_header(header: &[u8], max_payload_len: usize) -> Result<usize, BleFrameError> {
106    debug_assert!(header.len() >= BLE_V2_HEADER_LEN);
107    if header[..2] != BLE_V2_MAGIC {
108        return Err(BleFrameError::InvalidMagic);
109    }
110    if header[2] != BLE_V2_VERSION {
111        return Err(BleFrameError::UnsupportedVersion(header[2]));
112    }
113    if header[3] != 0 {
114        return Err(BleFrameError::UnsupportedFlags(header[3]));
115    }
116    let payload_len = u16::from_be_bytes([header[4], header[5]]) as usize;
117    if payload_len == 0 {
118        return Err(BleFrameError::EmptyPayload);
119    }
120    if payload_len > max_payload_len {
121        return Err(BleFrameError::Oversized {
122            payload_len,
123            max_payload_len,
124        });
125    }
126    Ok(payload_len)
127}
128
129/// Packet-preserving FIPS view over an ordered BLE byte stream.
130///
131/// Frames are segmented into raw writes no larger than the platform-reported
132/// send MTU. The send mutex prevents concurrent FIPS packets from interleaving.
133pub struct FramedBleStream<S> {
134    raw: S,
135    max_payload: u16,
136    send_lock: Mutex<()>,
137    receive: Mutex<ReceiveState>,
138}
139
140struct ReceiveState {
141    decoder: BleFrameDecoder,
142    pending: VecDeque<Vec<u8>>,
143    raw_buffer: Vec<u8>,
144}
145
146impl<S: BleStream> FramedBleStream<S> {
147    pub fn new(raw: S, max_payload: u16) -> Self {
148        let raw_buffer_len = usize::from(raw.recv_mtu()).max(BLE_V2_HEADER_LEN);
149        Self {
150            raw,
151            max_payload,
152            send_lock: Mutex::new(()),
153            receive: Mutex::new(ReceiveState {
154                decoder: BleFrameDecoder::new(max_payload.into()),
155                pending: VecDeque::new(),
156                raw_buffer: vec![0; raw_buffer_len],
157            }),
158        }
159    }
160
161    fn copy_packet(packet: &[u8], output: &mut [u8]) -> Result<usize, TransportError> {
162        if packet.len() > output.len() {
163            return Err(TransportError::RecvFailed(format!(
164                "BLE v2 frame {} exceeds receive buffer {}",
165                packet.len(),
166                output.len()
167            )));
168        }
169        output[..packet.len()].copy_from_slice(packet);
170        Ok(packet.len())
171    }
172}
173
174impl<S: BleStream> BleStream for FramedBleStream<S> {
175    async fn send(&self, data: &[u8]) -> Result<(), TransportError> {
176        let encoded = encode_frame(data, self.max_payload.into())
177            .map_err(|error| TransportError::SendFailed(error.to_string()))?;
178        let raw_mtu = usize::from(self.raw.send_mtu()).max(1);
179        let _guard = self.send_lock.lock().await;
180        for chunk in encoded.chunks(raw_mtu) {
181            self.raw.send(chunk).await?;
182        }
183        Ok(())
184    }
185
186    async fn recv(&self, output: &mut [u8]) -> Result<usize, TransportError> {
187        let mut state = self.receive.lock().await;
188        if let Some(packet) = state.pending.pop_front() {
189            return Self::copy_packet(&packet, output);
190        }
191
192        loop {
193            let mut raw_buffer = std::mem::take(&mut state.raw_buffer);
194            let received = self.raw.recv(&mut raw_buffer).await?;
195            if received == 0 {
196                let had_partial_frame = state.decoder.buffered_len() != 0;
197                state.raw_buffer = raw_buffer;
198                return if had_partial_frame {
199                    Err(TransportError::RecvFailed(
200                        "BLE v2 stream closed in the middle of a frame".into(),
201                    ))
202                } else {
203                    Ok(0)
204                };
205            }
206
207            let decoded = state
208                .decoder
209                .push(&raw_buffer[..received])
210                .map_err(|error| TransportError::RecvFailed(error.to_string()))?;
211            state.raw_buffer = raw_buffer;
212            state.pending.extend(decoded);
213            if let Some(packet) = state.pending.pop_front() {
214                return Self::copy_packet(&packet, output);
215            }
216        }
217    }
218
219    fn send_mtu(&self) -> u16 {
220        self.max_payload
221    }
222
223    fn recv_mtu(&self) -> u16 {
224        self.max_payload
225    }
226
227    fn remote_addr(&self) -> &BleAddr {
228        self.raw.remote_addr()
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use crate::transport::ble::{addr::BleAddr, io::MockBleStream};
236
237    const MAX_PAYLOAD: usize = 2048;
238
239    #[test]
240    fn encodes_v2_header_and_payload() {
241        let encoded = encode_frame(&[1, 2, 3], MAX_PAYLOAD).unwrap();
242        assert_eq!(
243            encoded,
244            [
245                BLE_V2_MAGIC.as_slice(),
246                &[BLE_V2_VERSION, 0, 0, 3],
247                &[1, 2, 3]
248            ]
249            .concat()
250        );
251    }
252
253    #[test]
254    fn decodes_a_frame_delivered_one_byte_at_a_time() {
255        let encoded = encode_frame(b"fragmented", MAX_PAYLOAD).unwrap();
256        let mut decoder = BleFrameDecoder::new(MAX_PAYLOAD);
257        let mut packets = Vec::new();
258        for byte in encoded {
259            packets.extend(decoder.push(&[byte]).unwrap());
260        }
261        assert_eq!(packets, vec![b"fragmented".to_vec()]);
262        assert_eq!(decoder.buffered_len(), 0);
263    }
264
265    #[test]
266    fn decodes_multiple_coalesced_frames() {
267        let bytes = [
268            encode_frame(b"one", MAX_PAYLOAD).unwrap(),
269            encode_frame(b"two", MAX_PAYLOAD).unwrap(),
270        ]
271        .concat();
272        let mut decoder = BleFrameDecoder::new(MAX_PAYLOAD);
273        assert_eq!(
274            decoder.push(&bytes).unwrap(),
275            vec![b"one".to_vec(), b"two".to_vec()]
276        );
277        assert_eq!(decoder.buffered_len(), 0);
278    }
279
280    #[test]
281    fn rejects_oversized_length_before_payload_arrives() {
282        let mut decoder = BleFrameDecoder::new(32);
283        let header = [BLE_V2_MAGIC[0], BLE_V2_MAGIC[1], BLE_V2_VERSION, 0, 0, 33];
284        assert_eq!(
285            decoder.push(&header),
286            Err(BleFrameError::Oversized {
287                payload_len: 33,
288                max_payload_len: 32,
289            })
290        );
291        assert!(decoder.buffered_len() <= BLE_V2_HEADER_LEN);
292    }
293
294    #[test]
295    fn rejects_empty_payloads() {
296        assert_eq!(
297            encode_frame(&[], MAX_PAYLOAD),
298            Err(BleFrameError::EmptyPayload)
299        );
300    }
301
302    #[test]
303    fn rejects_wrong_magic_version_and_flags() {
304        let mut decoder = BleFrameDecoder::new(MAX_PAYLOAD);
305        assert_eq!(
306            decoder.push(&[b'X', b'B', BLE_V2_VERSION, 0, 0, 1]),
307            Err(BleFrameError::InvalidMagic)
308        );
309
310        let mut decoder = BleFrameDecoder::new(MAX_PAYLOAD);
311        assert_eq!(
312            decoder.push(&[b'F', b'B', 3, 0, 0, 1]),
313            Err(BleFrameError::UnsupportedVersion(3))
314        );
315
316        let mut decoder = BleFrameDecoder::new(MAX_PAYLOAD);
317        assert_eq!(
318            decoder.push(&[b'F', b'B', BLE_V2_VERSION, 1, 0, 1]),
319            Err(BleFrameError::UnsupportedFlags(1))
320        );
321    }
322
323    #[tokio::test]
324    async fn framed_stream_segments_and_reassembles_packets() {
325        let left = BleAddr::from_mac("hci0", [0, 0, 0, 0, 0, 1]);
326        let right = BleAddr::from_mac("hci0", [0, 0, 0, 0, 0, 2]);
327        let (a, b) = MockBleStream::pair(left, right, 4);
328        let a = FramedBleStream::new(a, 128);
329        let b = FramedBleStream::new(b, 128);
330
331        a.send(b"one packet split across raw writes").await.unwrap();
332        let mut output = [0u8; 128];
333        let received = b.recv(&mut output).await.unwrap();
334        assert_eq!(&output[..received], b"one packet split across raw writes");
335    }
336
337    #[tokio::test]
338    async fn framed_stream_preserves_consecutive_packet_boundaries() {
339        let left = BleAddr::from_mac("hci0", [0, 0, 0, 0, 0, 1]);
340        let right = BleAddr::from_mac("hci0", [0, 0, 0, 0, 0, 2]);
341        let (a, b) = MockBleStream::pair(left, right, 64);
342        let a = FramedBleStream::new(a, 128);
343        let b = FramedBleStream::new(b, 128);
344
345        a.send(b"one").await.unwrap();
346        a.send(b"two").await.unwrap();
347
348        let mut output = [0u8; 128];
349        let first = b.recv(&mut output).await.unwrap();
350        assert_eq!(&output[..first], b"one");
351        let second = b.recv(&mut output).await.unwrap();
352        assert_eq!(&output[..second], b"two");
353    }
354}