1use buffa::{DecodeOptions, Message};
4
5use crate::MAX_FRAME_BYTES;
6
7pub const FRAME_RECURSION_LIMIT: u32 = 16;
13
14#[must_use]
24pub fn frame_decode_options() -> DecodeOptions {
25 DecodeOptions::new()
26 .with_recursion_limit(FRAME_RECURSION_LIMIT)
27 .with_max_message_size(MAX_FRAME_BYTES as usize)
28}
29
30#[derive(Debug, thiserror::Error)]
35pub enum FrameError {
36 #[error("frame length prefix truncated")]
38 LengthTruncated,
39
40 #[error("frame length {0} exceeds MAX_FRAME_BYTES")]
43 LengthTooLarge(u32),
44
45 #[error("frame body truncated: expected {expected} bytes, got {actual}")]
48 BodyTruncated { expected: u32, actual: usize },
49
50 #[error("frame decode failed")]
52 DecodeFailed,
53
54 #[error("io: {0}")]
56 Io(#[from] std::io::Error),
57}
58
59pub fn write_frame<W, M>(w: &mut W, msg: &M) -> Result<(), FrameError>
64where
65 W: std::io::Write,
66 M: Message,
67{
68 let body = msg.encode_to_vec();
69 let len: u32 = body
70 .len()
71 .try_into()
72 .map_err(|_| FrameError::LengthTooLarge(u32::MAX))?;
73 if len > MAX_FRAME_BYTES {
74 return Err(FrameError::LengthTooLarge(len));
75 }
76 w.write_all(&len.to_le_bytes())?;
77 w.write_all(&body)?;
78 w.flush()?;
79 Ok(())
80}
81
82pub fn read_frame<R, M>(r: &mut R) -> Result<M, FrameError>
86where
87 R: std::io::Read,
88 M: Message + Default,
89{
90 let mut len_buf = [0u8; 4];
91 r.read_exact(&mut len_buf).map_err(|e| {
92 if e.kind() == std::io::ErrorKind::UnexpectedEof {
93 FrameError::LengthTruncated
94 } else {
95 FrameError::Io(e)
96 }
97 })?;
98 let len = u32::from_le_bytes(len_buf);
99 if len > MAX_FRAME_BYTES {
100 return Err(FrameError::LengthTooLarge(len));
101 }
102
103 let mut body = vec![0u8; len as usize];
107 let mut filled = 0usize;
108 while filled < body.len() {
109 match r.read(&mut body[filled..]) {
110 Ok(0) => {
111 return Err(FrameError::BodyTruncated {
112 expected: len,
113 actual: filled,
114 });
115 }
116 Ok(n) => filled += n,
117 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
118 Err(e) => return Err(FrameError::Io(e)),
119 }
120 }
121
122 frame_decode_options()
123 .decode_from_slice(&body)
124 .map_err(|_| FrameError::DecodeFailed)
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::mkit::rpc::v1::signer::{SignerFrame, signer_frame};
131 use crate::mkit::rpc::v1::{Error, ErrorCode};
132 use std::io::Cursor;
133
134 fn err_frame(code: ErrorCode, msg: &str) -> SignerFrame {
135 SignerFrame {
136 body: Some(signer_frame::Body::Error(Box::new(
137 Error::default()
138 .with_code(code)
139 .with_message(msg)
140 .with_details(Vec::new()),
141 ))),
142 ..Default::default()
143 }
144 }
145
146 #[test]
147 fn roundtrip_signer_error_frame() {
148 let in_msg = err_frame(ErrorCode::UserDeclined, "user said no");
149 let mut buf = Vec::new();
150 write_frame(&mut buf, &in_msg).expect("write");
151
152 let advertised = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
154 assert_eq!(advertised as usize, buf.len() - 4);
155
156 let mut cur = Cursor::new(buf);
157 let out: SignerFrame = read_frame(&mut cur).expect("read");
158 assert_eq!(in_msg, out);
159 }
160
161 #[test]
162 fn read_rejects_oversized_frame() {
163 let mut buf = Vec::with_capacity(4);
165 buf.extend_from_slice(&(MAX_FRAME_BYTES + 1).to_le_bytes());
166 let mut cur = Cursor::new(buf);
167 match read_frame::<_, SignerFrame>(&mut cur) {
168 Err(FrameError::LengthTooLarge(n)) => assert_eq!(n, MAX_FRAME_BYTES + 1),
169 other => panic!("expected LengthTooLarge, got {other:?}"),
170 }
171 }
172
173 #[test]
174 fn read_rejects_truncated_length_prefix() {
175 let buf = vec![0x01, 0x00];
176 let mut cur = Cursor::new(buf);
177 match read_frame::<_, SignerFrame>(&mut cur) {
178 Err(FrameError::LengthTruncated) => {}
179 other => panic!("expected LengthTruncated, got {other:?}"),
180 }
181 }
182
183 #[test]
184 fn decode_options_reject_oversized_body_even_without_framing() {
185 use crate::mkit::rpc::v1::signer::SignRequest;
186
187 let frame = SignerFrame {
194 body: Some(signer_frame::Body::SignRequest(Box::new(
195 SignRequest::default().with_payload(vec![0u8; MAX_FRAME_BYTES as usize + 1]),
196 ))),
197 ..Default::default()
198 };
199 let bytes = frame.encode_to_vec();
200 assert!(SignerFrame::decode_from_slice(&bytes).is_ok());
201 assert!(
202 frame_decode_options()
203 .decode_from_slice::<SignerFrame>(&bytes)
204 .is_err(),
205 "decode cap must reject a body over MAX_FRAME_BYTES"
206 );
207 }
208
209 #[test]
210 fn body_truncated_reports_true_actual_count() {
211 let mut buf = Vec::new();
214 buf.extend_from_slice(&10u32.to_le_bytes());
215 buf.extend_from_slice(&[0xAA, 0xBB, 0xCC]); let mut cur = Cursor::new(buf);
217 match read_frame::<_, SignerFrame>(&mut cur) {
218 Err(FrameError::BodyTruncated { expected, actual }) => {
219 assert_eq!(expected, 10);
220 assert_eq!(actual, 3, "actual byte count must reflect bytes read");
221 }
222 other => panic!("expected BodyTruncated, got {other:?}"),
223 }
224 }
225}
226
227#[cfg(kani)]
235mod kani_proofs {
236 use super::*;
237 use buffa::bytes::Buf;
238 use buffa::encoding::Tag;
239 use buffa::{DecodeContext, DecodeError, DefaultInstance, EncodeSink, SizeCache};
240
241 #[derive(Clone, Debug, Default, PartialEq)]
244 struct Opaque;
245
246 impl DefaultInstance for Opaque {
247 fn default_instance() -> &'static Self {
248 &Opaque
249 }
250 }
251
252 impl Message for Opaque {
253 fn compute_size(&self, _cache: &mut SizeCache) -> u32 {
254 0
255 }
256 fn write_to(&self, _cache: &mut SizeCache, _buf: &mut impl EncodeSink) {}
257 fn merge(
258 &mut self,
259 buf: &mut impl Buf,
260 _ctx: DecodeContext<'_>,
261 ) -> Result<(), DecodeError> {
262 buf.advance(buf.remaining());
263 Ok(())
264 }
265 fn merge_field(
266 &mut self,
267 _tag: Tag,
268 _buf: &mut impl Buf,
269 _ctx: DecodeContext<'_>,
270 ) -> Result<(), DecodeError> {
271 Ok(())
272 }
273 fn clear(&mut self) {}
274 }
275
276 macro_rules! each_len {
279 ($f:ident; $($n:literal)*) => { $( $f::<$n>(); )* };
280 }
281
282 fn read_at<const N: usize>() {
283 let buf: [u8; N] = kani::any();
284 let b: &[u8] = &buf;
285 let prefix: Option<u32> = b
286 .get(..4)
287 .map(|p| u32::from_le_bytes(p.try_into().expect("4")));
288 let mut r = std::io::Cursor::new(b);
289 let got = read_frame::<_, Opaque>(&mut r);
290 let expect_ok = prefix.is_some_and(|l| l <= MAX_FRAME_BYTES && l as usize <= N - 4);
292 assert_eq!(got.is_ok(), expect_ok);
293 match got {
294 Err(FrameError::LengthTruncated) => {
295 assert!(N < 4);
296 }
297 Err(FrameError::LengthTooLarge(l)) => {
298 assert!(l > MAX_FRAME_BYTES);
299 }
300 Err(FrameError::BodyTruncated { expected, actual }) => {
301 assert!(actual < expected as usize && actual == N - 4);
302 }
303 Err(FrameError::DecodeFailed | FrameError::Io(_)) => {
304 panic!("an opaque body over an in-memory reader cannot fail to decode");
305 }
306 Ok(_) => {}
307 }
308 }
309
310 #[kani::proof]
315 #[kani::unwind(5)]
316 fn rpc_read_frame_no_panic() {
317 each_len!(read_at; 0 1 2 3 4 5 6);
318 }
319
320 #[kani::proof]
324 #[kani::unwind(5)]
325 #[kani::should_panic]
326 fn rpc_canary_over_cap_length_accepted() {
327 let buf: [u8; 4] = kani::any();
328 let mut r = std::io::Cursor::new(&buf[..]);
329 assert!(!matches!(
330 read_frame::<_, Opaque>(&mut r),
331 Err(FrameError::LengthTooLarge(_))
332 ));
333 }
334}