1pub const SAR_COMPLETE: u8 = 0;
9pub const SAR_FIRST: u8 = 1;
11pub const SAR_CONT: u8 = 2;
13pub const SAR_LAST: u8 = 3;
15
16const SAR_SHIFT: u8 = 6;
17const RESERVED_MASK: u8 = 0x3f;
18
19pub const MAX_FRAME: usize = 512;
21
22pub const fn uuid(slot: u16) -> u128 {
24 0x21EB_6B15_0000_4CCF_92E4_A079_171B_EC97u128 | ((slot as u128) << 80)
25}
26
27pub const SERVICE_UUID: u128 = uuid(0x0001);
29pub const FRAME_IN_UUID: u128 = uuid(0x0002);
31pub const FRAME_OUT_UUID: u128 = uuid(0x0003);
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct Segment<'a> {
37 sar: u8,
38 payload: &'a [u8],
39}
40
41impl<'a> Segment<'a> {
42 pub const fn header(&self) -> u8 {
44 self.sar << SAR_SHIFT
45 }
46
47 pub const fn payload(&self) -> &'a [u8] {
49 self.payload
50 }
51
52 pub fn write_to(&self, out: &mut [u8]) -> Result<usize, EncodeError> {
54 let len = self.payload.len() + 1;
55 if out.len() < len {
56 return Err(EncodeError::BufferTooSmall);
57 }
58 out[0] = self.header();
59 out[1..len].copy_from_slice(self.payload);
60 Ok(len)
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum EncodeError {
67 BufferTooSmall,
69}
70
71pub struct Segments<'a> {
73 frame: &'a [u8],
74 seg_payload: usize,
75 offset: usize,
76 emitted_empty: bool,
77}
78
79impl<'a> Iterator for Segments<'a> {
80 type Item = Segment<'a>;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 if self.frame.is_empty() {
84 if self.emitted_empty {
85 return None;
86 }
87 self.emitted_empty = true;
88 return Some(Segment {
89 sar: SAR_COMPLETE,
90 payload: self.frame,
91 });
92 }
93 if self.offset >= self.frame.len() {
94 return None;
95 }
96
97 let start = self.offset;
98 let end = start.saturating_add(self.seg_payload).min(self.frame.len());
99 self.offset = end;
100 let sar = if self.frame.len() <= self.seg_payload {
101 SAR_COMPLETE
102 } else if start == 0 {
103 SAR_FIRST
104 } else if end == self.frame.len() {
105 SAR_LAST
106 } else {
107 SAR_CONT
108 };
109 Some(Segment {
110 sar,
111 payload: &self.frame[start..end],
112 })
113 }
114
115 fn size_hint(&self) -> (usize, Option<usize>) {
116 let remaining = if self.frame.is_empty() {
117 usize::from(!self.emitted_empty)
118 } else {
119 (self.frame.len() - self.offset).div_ceil(self.seg_payload)
120 };
121 (remaining, Some(remaining))
122 }
123}
124
125impl ExactSizeIterator for Segments<'_> {}
126
127pub fn segments(frame: &[u8], seg_payload: usize) -> Segments<'_> {
132 assert!(seg_payload >= 1, "GATT segment payload must be nonzero");
133 Segments {
134 frame,
135 seg_payload,
136 offset: 0,
137 emitted_empty: false,
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum DecodeError {
144 ReservedBits,
146 Orphan,
148 TooLong,
150 Runt,
152}
153
154pub struct Reassembler<const N: usize> {
156 buf: [u8; N],
157 len: usize,
158 in_progress: bool,
159 discarding_overflow: bool,
160}
161
162impl<const N: usize> Default for Reassembler<N> {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl<const N: usize> Reassembler<N> {
169 pub const fn new() -> Self {
171 Self {
172 buf: [0; N],
173 len: 0,
174 in_progress: false,
175 discarding_overflow: false,
176 }
177 }
178
179 pub fn reset(&mut self) {
181 self.len = 0;
182 self.in_progress = false;
183 self.discarding_overflow = false;
184 }
185
186 pub fn push(&mut self, segment: &[u8]) -> Option<Result<&[u8], DecodeError>> {
192 let Some((&header, payload)) = segment.split_first() else {
193 self.reset();
194 return Some(Err(DecodeError::Runt));
195 };
196 if header & RESERVED_MASK != 0 {
197 self.reset();
198 return Some(Err(DecodeError::ReservedBits));
199 }
200 let sar = header >> SAR_SHIFT;
201
202 if matches!(sar, SAR_COMPLETE | SAR_FIRST) {
203 self.reset();
204 } else if self.discarding_overflow {
205 return None;
206 } else if !self.in_progress {
207 return Some(Err(DecodeError::Orphan));
208 }
209
210 if payload.len() > N.saturating_sub(self.len) {
211 self.len = 0;
212 self.in_progress = false;
213 self.discarding_overflow = true;
214 return Some(Err(DecodeError::TooLong));
215 }
216 self.buf[self.len..self.len + payload.len()].copy_from_slice(payload);
217 self.len += payload.len();
218
219 match sar {
220 SAR_COMPLETE => {
221 self.in_progress = false;
222 Some(Ok(&self.buf[..self.len]))
223 }
224 SAR_FIRST | SAR_CONT => {
225 self.in_progress = true;
226 None
227 }
228 SAR_LAST => {
229 self.in_progress = false;
230 Some(Ok(&self.buf[..self.len]))
231 }
232 _ => unreachable!(),
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 fn encoded(segment: Segment<'_>) -> std::vec::Vec<u8> {
242 let mut out = std::vec![0; segment.payload().len() + 1];
243 let len = segment.write_to(&mut out).unwrap();
244 out.truncate(len);
245 out
246 }
247
248 fn round_trip(frame: &[u8], size: usize) {
249 let mut decoder = Reassembler::<MAX_FRAME>::new();
250 let mut result = None;
251 for segment in segments(frame, size) {
252 if let Some(decoded) = decoder.push(&encoded(segment)) {
253 result = Some(decoded.unwrap().to_vec());
254 }
255 }
256 assert_eq!(result.as_deref(), Some(frame));
257 }
258
259 #[test]
260 fn round_trip_common_mtu_payloads() {
261 let frame: std::vec::Vec<u8> = (0..512).map(|n| n as u8).collect();
262 for size in [19, 243, 511] {
263 round_trip(&frame, size);
264 }
265 }
266
267 #[test]
268 fn complete_exact_empty_and_one_byte_payloads() {
269 round_trip(b"one segment", 19);
270 round_trip(&[7; 38], 19);
271 round_trip(&[], 19);
272 round_trip(&[1, 2, 3], 1);
273 let empty = segments(&[], 1).next().unwrap();
274 assert_eq!(empty.header(), 0);
275 assert!(empty.payload().is_empty());
276 }
277
278 #[test]
279 fn empty_middle_segment_is_legal() {
280 let mut r = Reassembler::<8>::new();
281 assert_eq!(r.push(&[SAR_FIRST << 6, 1]), None);
282 assert_eq!(r.push(&[SAR_CONT << 6]), None);
283 assert_eq!(r.push(&[SAR_LAST << 6, 2]), Some(Ok(&[1, 2][..])));
284 }
285
286 #[test]
287 fn rejects_reserved_orphan_and_runt() {
288 let mut r = Reassembler::<8>::new();
289 assert_eq!(r.push(&[1, 2]), Some(Err(DecodeError::ReservedBits)));
290 assert_eq!(r.push(&[SAR_CONT << 6, 2]), Some(Err(DecodeError::Orphan)));
291 assert_eq!(r.push(&[SAR_LAST << 6]), Some(Err(DecodeError::Orphan)));
292 assert_eq!(r.push(&[]), Some(Err(DecodeError::Runt)));
293 assert_eq!(r.push(&[SAR_FIRST << 6, 1]), None);
294 assert_eq!(r.push(&[]), Some(Err(DecodeError::Runt)));
295 assert_eq!(r.push(&[SAR_LAST << 6, 2]), Some(Err(DecodeError::Orphan)));
296 }
297
298 #[test]
299 fn first_discards_partial_and_restarts() {
300 let mut r = Reassembler::<8>::new();
301 assert_eq!(r.push(&[SAR_FIRST << 6, 1]), None);
302 assert_eq!(r.push(&[SAR_FIRST << 6, 2]), None);
303 assert_eq!(r.push(&[SAR_LAST << 6, 3]), Some(Ok(&[2, 3][..])));
304 }
305
306 #[test]
307 fn overflow_reports_once_then_recovers_at_start() {
308 let mut r = Reassembler::<3>::new();
309 assert_eq!(r.push(&[SAR_FIRST << 6, 1, 2]), None);
310 assert_eq!(
311 r.push(&[SAR_CONT << 6, 3, 4]),
312 Some(Err(DecodeError::TooLong))
313 );
314 assert_eq!(r.push(&[SAR_LAST << 6, 5]), None);
315 assert_eq!(r.push(&[SAR_COMPLETE << 6, 9]), Some(Ok(&[9][..])));
316 }
317
318 #[test]
319 #[should_panic(expected = "GATT segment payload must be nonzero")]
320 fn zero_segment_payload_panics_unconditionally() {
321 let _ = segments(b"frame", 0);
322 }
323
324 #[test]
325 fn uuid_literals_match_spec() {
326 assert_eq!(SERVICE_UUID, 0x21EB6B1500014CCF92E4A079171BEC97);
327 assert_eq!(FRAME_IN_UUID, 0x21EB6B1500024CCF92E4A079171BEC97);
328 assert_eq!(FRAME_OUT_UUID, 0x21EB6B1500034CCF92E4A079171BEC97);
329 }
330}