umsh_node_mgmt/
fragment.rs1use umsh_ulcp::frame::{Cmd, Frame};
15use umsh_ulcp::pui;
16
17use crate::device::{Dispatch, Produced};
18
19pub const fn continuable(cmd: Cmd) -> bool {
26 matches!(cmd, Cmd::PropGet | Cmd::PropMultiGet)
27}
28
29pub fn trailing_offset(frame: &[u8]) -> Option<usize> {
36 let parsed = Frame::parse(frame).ok()?;
37 let offset = frame.len().checked_sub(parsed.payload.len())?;
38 match parsed.command()? {
39 Cmd::PropIs => {
41 let (_, consumed) = pui::decode(parsed.payload).ok()?;
42 Some(offset + consumed)
43 }
44 Cmd::PropAre => Some(offset),
46 _ => None,
47 }
48}
49
50pub fn trailing(frame: &[u8]) -> &[u8] {
52 match trailing_offset(frame) {
53 Some(offset) => &frame[offset..],
54 None => &[],
55 }
56}
57
58pub fn produce<'b>(reply: &[u8], dispatch: &Dispatch<'_>, buf: &'b mut [u8]) -> Produced<'b> {
70 if reply.is_empty() {
71 return Produced::no_response();
72 }
73 let Some(offset) = trailing_offset(reply) else {
74 let len = reply.len().min(buf.len());
75 buf[..len].copy_from_slice(&reply[..len]);
76 return Produced::complete(&buf[..len]);
77 };
78 let (prefix, trailing) = reply.split_at(offset);
79 let prefix_len = prefix.len().min(buf.len());
80 let resume = (dispatch.resume as usize).min(trailing.len());
83 let available = trailing.len() - resume;
84 let room = dispatch
85 .budget
86 .saturating_sub(prefix_len)
87 .min(buf.len() - prefix_len);
88 let take = available.min(room);
89 let end = prefix_len + take;
90 buf[..prefix_len].copy_from_slice(&prefix[..prefix_len]);
91 buf[prefix_len..end].copy_from_slice(&trailing[resume..resume + take]);
92 Produced::fragment(&buf[..end], take as u32, (available - take) as u32)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use umsh_ulcp::frame;
99 use umsh_ulcp::ids::prop;
100
101 fn dispatch(resume: u32, budget: usize) -> Dispatch<'static> {
104 Dispatch {
105 frame: &[],
106 resume,
107 budget,
108 resets: false,
109 }
110 }
111
112 #[test]
113 fn a_reply_that_fits_is_carried_whole() {
114 let mut reply = [0u8; 64];
115 let len = frame::prop_is(&mut reply, 0, prop::DEV_PEERS, &[7; 32]).unwrap();
116 let mut buf = [0u8; 64];
117 let produced = produce(&reply[..len], &dispatch(0, 64), &mut buf);
118 assert_eq!(produced.frame, &reply[..len]);
119 assert_eq!(produced.remaining, 0);
120 }
121
122 #[test]
123 fn a_reply_that_does_not_fit_is_cut_at_the_budget() {
124 let mut reply = [0u8; 64];
125 let len = frame::prop_is(&mut reply, 0, prop::DEV_PEERS, &[7; 32]).unwrap();
126 let prefix = trailing_offset(&reply[..len]).unwrap();
127
128 let mut buf = [0u8; 64];
129 let first = produce(&reply[..len], &dispatch(0, prefix + 20), &mut buf);
130 assert_eq!(first.produced, 20);
131 assert_eq!(first.remaining, 12);
132 assert_eq!(&first.frame[..prefix], &reply[..prefix]);
133 assert_eq!(&first.frame[prefix..], &[7; 20]);
134
135 let mut buf = [0u8; 64];
138 let rest = produce(&reply[..len], &dispatch(20, prefix + 20), &mut buf);
139 assert_eq!(rest.produced, 12);
140 assert_eq!(rest.remaining, 0);
141 assert_eq!(&rest.frame[prefix..], &[7; 12]);
142 }
143
144 #[test]
146 fn the_buffer_bounds_the_cut_as_much_as_the_budget_does() {
147 let mut reply = [0u8; 64];
148 let len = frame::prop_is(&mut reply, 0, prop::DEV_PEERS, &[7; 32]).unwrap();
149 let prefix = trailing_offset(&reply[..len]).unwrap();
150 let mut buf = [0u8; 16];
151 let produced = produce(&reply[..len], &dispatch(0, 1024), &mut buf);
152 assert_eq!(produced.frame.len(), 16);
153 assert_eq!(produced.produced as usize, 16 - prefix);
154 assert_eq!(produced.remaining as usize, 32 - (16 - prefix));
155 }
156
157 #[test]
160 fn a_cursor_past_the_end_ends_the_read() {
161 let mut reply = [0u8; 64];
162 let len = frame::prop_is(&mut reply, 0, prop::DEV_PEERS, &[7; 32]).unwrap();
163 let prefix = trailing_offset(&reply[..len]).unwrap();
164 let mut buf = [0u8; 64];
165 let produced = produce(&reply[..len], &dispatch(99, 64), &mut buf);
166 assert_eq!(produced.frame.len(), prefix);
167 assert_eq!(produced.produced, 0);
168 assert_eq!(produced.remaining, 0);
169 }
170
171 #[test]
172 fn an_empty_reply_is_a_reset() {
173 let mut buf = [0u8; 8];
174 assert_eq!(
175 produce(&[], &dispatch(0, 64), &mut buf),
176 Produced::no_response()
177 );
178 }
179
180 #[test]
181 fn a_prop_is_is_cut_after_its_key() {
182 let mut buf = [0u8; 64];
183 let len = frame::prop_is(&mut buf, 0, prop::DEV_PEERS, &[7; 32]).unwrap();
184 assert_eq!(trailing(&buf[..len]), &[7; 32]);
185 }
186
187 #[test]
188 fn a_prop_are_is_cut_after_its_command() {
189 let entries = [0x03, 0x05, 0x01, 0x02];
191 let mut buf = [0u8; 16];
192 buf[0] = 0x80;
193 buf[1] = Cmd::PropAre as u8;
194 buf[2..2 + entries.len()].copy_from_slice(&entries);
195 assert_eq!(trailing(&buf[..2 + entries.len()]), &entries);
196 }
197
198 #[test]
199 fn everything_else_has_no_trailing_content() {
200 let mut buf = [0u8; 32];
201 let len = frame::last_status(&mut buf, 0, umsh_ulcp::status::Status::OK).unwrap();
202 assert!(!trailing(&buf[..len]).is_empty());
205
206 let len = frame::prop_inserted(&mut buf, 0, prop::DEV_PEERS, &[1, 2]).unwrap();
207 assert!(trailing(&buf[..len]).is_empty());
208 assert_eq!(trailing_offset(&buf[..len]), None);
209 }
210
211 #[test]
212 fn only_reads_may_be_continued() {
213 assert!(continuable(Cmd::PropGet));
214 assert!(continuable(Cmd::PropMultiGet));
215 assert!(!continuable(Cmd::PropMultiSet));
216 assert!(!continuable(Cmd::PropSet));
217 assert!(!continuable(Cmd::Save));
218 }
219}