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}