termwright_protocol/
framing.rs1use serde::Serialize;
9use serde_json::Value;
10
11use crate::error::Violation;
12
13pub const FRAME_HEADER_BYTES: usize = 4;
15
16const RESERVED_KEYS: [&str; 3] = ["__proto__", "constructor", "prototype"];
20
21pub fn encode_frame<T: Serialize>(value: &T, max_frame_bytes: usize) -> Result<Vec<u8>, Violation> {
30 if max_frame_bytes == 0 {
31 return Err(Violation::new(
32 "frame-malformed",
33 "maxFrameBytes must be positive",
34 ));
35 }
36 let body = serde_json::to_vec(value)
37 .map_err(|_| Violation::new("frame-malformed", "message is not JSON-serialisable"))?;
38 if body.len() > max_frame_bytes {
39 return Err(Violation::new(
40 "frame-oversized",
41 format!(
42 "encoded frame is {} bytes, ceiling is {max_frame_bytes}",
43 body.len()
44 ),
45 ));
46 }
47 let mut frame = Vec::with_capacity(FRAME_HEADER_BYTES + body.len());
48 frame.extend_from_slice(&(body.len() as u32).to_be_bytes());
49 frame.extend_from_slice(&body);
50 Ok(frame)
51}
52
53pub fn decode_body(body: &[u8], max_depth: usize) -> Result<Value, Violation> {
59 let text = std::str::from_utf8(body)
60 .map_err(|_| Violation::new("frame-encoding", "frame body is not valid UTF-8"))?;
61 let value: Value = serde_json::from_str(text)
62 .map_err(|_| Violation::new("frame-malformed", "frame body is not valid JSON"))?;
63 project_dto(&value, max_depth)?;
64 Ok(value)
65}
66
67pub fn project_dto(value: &Value, max_depth: usize) -> Result<(), Violation> {
77 project_node(value, 0, max_depth, "$")
78}
79
80fn project_node(
81 value: &Value,
82 depth: usize,
83 max_depth: usize,
84 path: &str,
85) -> Result<(), Violation> {
86 match value {
87 Value::Array(items) => {
88 if depth > max_depth {
89 return Err(depth_violation(max_depth, path));
90 }
91 for (index, item) in items.iter().enumerate() {
92 project_node(item, depth + 1, max_depth, &format!("{path}[{index}]"))?;
93 }
94 Ok(())
95 }
96 Value::Object(entries) => {
97 if depth > max_depth {
98 return Err(depth_violation(max_depth, path));
99 }
100 for (key, item) in entries {
101 if RESERVED_KEYS.contains(&key.as_str()) {
102 return Err(Violation::new(
103 "dto-key",
104 format!("reserved property name \"{key}\" at {path}"),
105 ));
106 }
107 project_node(item, depth + 1, max_depth, &format!("{path}.{key}"))?;
108 }
109 Ok(())
110 }
111 _ => Ok(()),
112 }
113}
114
115fn depth_violation(max_depth: usize, path: &str) -> Violation {
116 Violation::new(
117 "dto-depth",
118 format!("nesting exceeds {max_depth} at {path}"),
119 )
120}
121
122#[derive(Debug, Clone)]
124pub struct Frame {
125 pub raw: Vec<u8>,
127 pub value: Value,
129}
130
131#[derive(Debug)]
133pub struct FrameDecoder {
134 max_frame_bytes: usize,
135 max_depth: usize,
136 buffer: Vec<u8>,
137 failure: Option<Violation>,
138}
139
140impl FrameDecoder {
141 pub fn new(max_frame_bytes: usize, max_depth: usize) -> Self {
143 Self {
144 max_frame_bytes,
145 max_depth,
146 buffer: Vec::new(),
147 failure: None,
148 }
149 }
150
151 pub fn buffered(&self) -> usize {
153 self.buffer.len()
154 }
155
156 pub fn push(&mut self, chunk: &[u8]) -> Result<Vec<Frame>, Violation> {
162 if let Some(failure) = &self.failure {
163 return Err(Violation::new(
164 "decoder-poisoned",
165 format!(
166 "decoder failed earlier ({}) and accepts no further input",
167 failure.code
168 ),
169 ));
170 }
171 match self.push_inner(chunk) {
172 Ok(frames) => Ok(frames),
173 Err(violation) => {
174 self.failure = Some(violation.clone());
175 self.buffer = Vec::new();
176 Err(violation)
177 }
178 }
179 }
180
181 fn push_inner(&mut self, chunk: &[u8]) -> Result<Vec<Frame>, Violation> {
182 self.buffer.extend_from_slice(chunk);
183 let mut frames = Vec::new();
184 let mut offset = 0usize;
185
186 while self.buffer.len() - offset >= FRAME_HEADER_BYTES {
187 let header: [u8; FRAME_HEADER_BYTES] = self.buffer[offset..offset + FRAME_HEADER_BYTES]
188 .try_into()
189 .expect("slice length checked above");
190 let length = u32::from_be_bytes(header) as usize;
191 if length == 0 {
192 return Err(Violation::new(
193 "frame-malformed",
194 "frame length must be non-zero",
195 ));
196 }
197 if length > self.max_frame_bytes {
198 return Err(Violation::new(
199 "frame-oversized",
200 format!(
201 "frame declares {length} bytes, ceiling is {}",
202 self.max_frame_bytes
203 ),
204 ));
205 }
206 let end = offset + FRAME_HEADER_BYTES + length;
207 if self.buffer.len() < end {
208 break;
209 }
210 let body = &self.buffer[offset + FRAME_HEADER_BYTES..end];
211 let value = decode_body(body, self.max_depth)?;
212 frames.push(Frame {
213 raw: body.to_vec(),
214 value,
215 });
216 offset = end;
217 }
218
219 if offset > 0 {
220 self.buffer.drain(..offset);
221 }
222 if self.buffer.len() > self.max_frame_bytes + FRAME_HEADER_BYTES {
223 return Err(Violation::new(
224 "frame-oversized",
225 format!(
226 "buffered {} bytes without a complete frame",
227 self.buffer.len()
228 ),
229 ));
230 }
231 Ok(frames)
232 }
233}