Skip to main content

rns_embedded_runtime/
ble.rs

1use alloc::{collections::VecDeque, vec::Vec};
2use rns_embedded_core::{
3    packet::{decode_frame, encode_frame, PacketFrame},
4    transport::{EmbeddedTransport, LinkState, TransportCaps},
5    EmbeddedError, EmbeddedResult,
6};
7
8#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9pub struct BleShimConfig {
10    pub mtu_hint: u16,
11    pub max_inbound_frames: usize,
12    pub max_outbound_frames: usize,
13    pub ordered_delivery: bool,
14}
15
16impl Default for BleShimConfig {
17    fn default() -> Self {
18        Self {
19            mtu_hint: 244,
20            max_inbound_frames: 16,
21            max_outbound_frames: 16,
22            ordered_delivery: true,
23        }
24    }
25}
26
27pub struct BleShimTransport {
28    state: LinkState,
29    caps: TransportCaps,
30    max_inbound_frames: usize,
31    max_outbound_frames: usize,
32    inbound_frames: VecDeque<PacketFrame>,
33    outbound_wire: VecDeque<Vec<u8>>,
34}
35
36impl BleShimTransport {
37    pub fn new(config: BleShimConfig) -> EmbeddedResult<Self> {
38        if config.mtu_hint == 0 || config.max_inbound_frames == 0 || config.max_outbound_frames == 0
39        {
40            return Err(EmbeddedError::InvalidArgument);
41        }
42        Ok(Self {
43            state: LinkState::Down,
44            caps: TransportCaps {
45                mtu_hint: config.mtu_hint,
46                ordered_delivery: config.ordered_delivery,
47            },
48            max_inbound_frames: config.max_inbound_frames,
49            max_outbound_frames: config.max_outbound_frames,
50            inbound_frames: VecDeque::new(),
51            outbound_wire: VecDeque::new(),
52        })
53    }
54
55    pub fn set_link_state(&mut self, state: LinkState) {
56        self.state = state;
57    }
58
59    pub fn push_inbound_wire(&mut self, bytes: &[u8]) -> EmbeddedResult<()> {
60        if self.inbound_frames.len() >= self.max_inbound_frames {
61            return Err(EmbeddedError::Backpressure);
62        }
63        let frame = decode_frame(bytes)?;
64        self.inbound_frames.push_back(frame);
65        Ok(())
66    }
67
68    pub fn drain_outbound_wire(&mut self) -> Vec<Vec<u8>> {
69        self.outbound_wire.drain(..).collect()
70    }
71
72    pub fn take_outbound_wire(&mut self) -> Option<Vec<u8>> {
73        self.outbound_wire.pop_front()
74    }
75
76    pub fn pending_inbound_len(&self) -> usize {
77        self.inbound_frames.len()
78    }
79
80    pub fn pending_outbound_len(&self) -> usize {
81        self.outbound_wire.len()
82    }
83}
84
85impl EmbeddedTransport for BleShimTransport {
86    fn link_state(&self) -> LinkState {
87        self.state
88    }
89
90    fn capabilities(&self) -> TransportCaps {
91        self.caps
92    }
93
94    fn send_frame(&mut self, frame: &PacketFrame) -> EmbeddedResult<()> {
95        if self.state != LinkState::Up {
96            return Err(EmbeddedError::Disconnected);
97        }
98        if self.outbound_wire.len() >= self.max_outbound_frames {
99            return Err(EmbeddedError::Backpressure);
100        }
101        if frame.payload.len() > usize::from(self.caps.mtu_hint) {
102            return Err(EmbeddedError::InvalidArgument);
103        }
104        self.outbound_wire.push_back(encode_frame(frame)?);
105        Ok(())
106    }
107
108    fn poll_frame(&mut self) -> EmbeddedResult<Option<PacketFrame>> {
109        if self.state != LinkState::Up {
110            return Ok(None);
111        }
112        Ok(self.inbound_frames.pop_front())
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use alloc::{vec, vec::Vec};
119
120    use super::{BleShimConfig, BleShimTransport};
121    use crate::{
122        CaptureDefaults, EmbeddedNodeRuntime, NodeTransportMode, RuntimeConfig, FRAME_KIND_ANNOUNCE,
123    };
124    use rns_embedded_core::{
125        packet::{decode_frame, encode_frame, PacketFrame},
126        store::{EmbeddedStore, JournaledEmbeddedStore},
127        transport::{EmbeddedTransport, LinkState},
128        EmbeddedError,
129    };
130
131    fn runtime_config() -> RuntimeConfig {
132        RuntimeConfig {
133            store_identity: [0x5A; 32],
134            lxmf_address: [0xC3; 16],
135            node_mode: NodeTransportMode::BleOnly,
136            announce_interval_ms: 1_000,
137            max_outbound_queue: 8,
138            max_events: 16,
139            capture_defaults: CaptureDefaults::default(),
140        }
141    }
142
143    fn wire_frame(kind: u8, sequence: u32, payload: &[u8]) -> Vec<u8> {
144        let frame = PacketFrame::new(kind, sequence, payload.to_vec()).expect("frame");
145        encode_frame(&frame).expect("encode")
146    }
147
148    #[test]
149    fn shim_round_trips_wire_bytes() {
150        let mut shim = BleShimTransport::new(BleShimConfig::default()).expect("shim");
151        shim.set_link_state(LinkState::Up);
152        let input = wire_frame(0x41, 7, b"hello-ble");
153        shim.push_inbound_wire(&input).expect("push inbound");
154
155        let frame = shim.poll_frame().expect("poll").expect("frame");
156        assert_eq!(frame.kind, 0x41);
157        assert_eq!(frame.sequence, 7);
158        assert_eq!(frame.payload, b"hello-ble");
159
160        shim.send_frame(&frame).expect("send frame");
161        let outbound = shim.drain_outbound_wire();
162        assert_eq!(outbound, vec![input]);
163    }
164
165    #[test]
166    fn shim_enforces_link_and_queue_limits() {
167        let mut shim = BleShimTransport::new(BleShimConfig {
168            mtu_hint: 32,
169            max_inbound_frames: 1,
170            max_outbound_frames: 1,
171            ordered_delivery: true,
172        })
173        .expect("shim");
174
175        let err = shim
176            .send_frame(&PacketFrame::new(0x11, 1, b"abc".to_vec()).expect("frame"))
177            .expect_err("disconnected");
178        assert_eq!(err, EmbeddedError::Disconnected);
179
180        assert_eq!(shim.poll_frame().expect("poll while down"), None);
181
182        shim.set_link_state(LinkState::Up);
183        shim.push_inbound_wire(&wire_frame(0x01, 1, b"x")).expect("first inbound");
184        let err =
185            shim.push_inbound_wire(&wire_frame(0x01, 2, b"y")).expect_err("inbound backpressure");
186        assert_eq!(err, EmbeddedError::Backpressure);
187
188        shim.send_frame(&PacketFrame::new(0x11, 1, b"abc".to_vec()).expect("frame"))
189            .expect("first outbound");
190        let err = shim
191            .send_frame(&PacketFrame::new(0x11, 2, b"def".to_vec()).expect("frame"))
192            .expect_err("outbound backpressure");
193        assert_eq!(err, EmbeddedError::Backpressure);
194    }
195
196    #[test]
197    fn runtime_tick_drains_announce_into_ble_wire() {
198        let mut runtime = EmbeddedNodeRuntime::new(runtime_config()).expect("runtime");
199        let mut store = JournaledEmbeddedStore::new();
200        let mut shim = BleShimTransport::new(BleShimConfig::default()).expect("shim");
201        shim.set_link_state(LinkState::Up);
202
203        runtime.tick(0, &mut shim, &mut store).expect("tick");
204
205        let outbound = shim.drain_outbound_wire();
206        assert_eq!(outbound.len(), 1);
207        let frame = decode_frame(&outbound[0]).expect("decode outbound");
208        assert_eq!(frame.kind, FRAME_KIND_ANNOUNCE);
209        assert_eq!(frame.sequence, 1);
210        assert_eq!(frame.payload, vec![0x5A; 32]);
211    }
212
213    #[test]
214    fn runtime_accepts_ble_wire_and_updates_replay_floor() {
215        let mut runtime = EmbeddedNodeRuntime::new(runtime_config()).expect("runtime");
216        let mut store = JournaledEmbeddedStore::new();
217        let mut shim = BleShimTransport::new(BleShimConfig::default()).expect("shim");
218        shim.set_link_state(LinkState::Up);
219
220        shim.push_inbound_wire(&wire_frame(0x45, 9, b"ping")).expect("push inbound");
221        runtime.tick(0, &mut shim, &mut store).expect("tick");
222
223        assert_eq!(
224            store.load_replay_floor(&runtime_config().store_identity).expect("load replay"),
225            9
226        );
227    }
228}