Skip to main content

ghidra_ipc/
frame.rs

1//! Length-delimited framing: 4-byte big-endian length prefix + UTF-8 JSON body (spec §5).
2
3use thiserror::Error;
4
5/// Hard cap on a single frame body (16 MiB). A length prefix larger than this is
6/// rejected before any allocation, so a garbage/hostile length can't OOM the host.
7pub const MAX_FRAME_LEN: usize = 16 * 1024 * 1024;
8
9#[derive(Debug, Error, PartialEq, Eq)]
10pub enum FrameError {
11    #[error("frame length {0} bytes exceeds MAX_FRAME_LEN ({MAX_FRAME_LEN})")]
12    TooLarge(usize),
13    /// The decoder saw an unrecoverable protocol violation (an oversize length) and is
14    /// permanently poisoned. An oversize prefix means the byte stream is corrupt/hostile and
15    /// cannot be resynced (we don't know where the next frame starts), so the caller MUST tear
16    /// down the connection rather than keep decoding.
17    #[error("frame decoder poisoned by a prior protocol violation")]
18    Poisoned,
19}
20
21/// Encode a payload as a 4-byte big-endian length prefix followed by the body.
22pub fn encode_frame(payload: &[u8]) -> Result<Vec<u8>, FrameError> {
23    if payload.len() > MAX_FRAME_LEN {
24        return Err(FrameError::TooLarge(payload.len()));
25    }
26    let mut out = Vec::with_capacity(4 + payload.len());
27    out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
28    out.extend_from_slice(payload);
29    Ok(out)
30}
31
32/// Incremental decoder: feed arbitrary chunks via `push`, pull complete frame
33/// bodies via `next_frame`. Tolerant of frames split across chunk boundaries.
34#[derive(Default)]
35pub struct FrameDecoder {
36    buf: Vec<u8>,
37    /// Read position into `buf`; consumed bytes are reclaimed by compaction, not per-frame drain.
38    pos: usize,
39    /// Set once an oversize length is seen. The stream is then unrecoverable, so every
40    /// later `next_frame` returns `Poisoned` instead of re-reading the same bad header
41    /// forever (the panel-found infinite-error-loop bug).
42    poisoned: bool,
43}
44
45impl FrameDecoder {
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Append received bytes to the internal buffer.
51    pub fn push(&mut self, data: &[u8]) {
52        self.buf.extend_from_slice(data);
53    }
54
55    /// Pull the next complete frame body if one is fully buffered.
56    /// `Ok(None)` = need more bytes. `Err(TooLarge)` on the first oversize length,
57    /// `Err(Poisoned)` on every call thereafter — the caller must drop the connection.
58    pub fn next_frame(&mut self) -> Result<Option<Vec<u8>>, FrameError> {
59        if self.poisoned {
60            return Err(FrameError::Poisoned);
61        }
62        let avail = self.buf.len() - self.pos;
63        if avail < 4 {
64            self.maybe_compact();
65            return Ok(None);
66        }
67        let p = self.pos;
68        let len = u32::from_be_bytes([
69            self.buf[p],
70            self.buf[p + 1],
71            self.buf[p + 2],
72            self.buf[p + 3],
73        ]) as usize;
74        if len > MAX_FRAME_LEN {
75            self.poisoned = true;
76            return Err(FrameError::TooLarge(len));
77        }
78        if avail < 4 + len {
79            self.maybe_compact();
80            return Ok(None);
81        }
82        let body = self.buf[p + 4..p + 4 + len].to_vec();
83        self.pos += 4 + len;
84        self.maybe_compact();
85        Ok(Some(body))
86    }
87
88    /// Drop already-consumed bytes once they exceed half the buffer, so long-running streams do
89    /// not grow `buf` unbounded and no per-frame O(n) drain is paid.
90    fn maybe_compact(&mut self) {
91        if self.pos > 0 && self.pos >= self.buf.len() / 2 {
92            self.buf.drain(..self.pos);
93            self.pos = 0;
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn round_trip_single_frame() {
104        let payload = br#"{"jsonrpc":"2.0"}"#;
105        let encoded = encode_frame(payload).unwrap();
106        assert_eq!(&encoded[..4], &(payload.len() as u32).to_be_bytes());
107
108        let mut dec = FrameDecoder::new();
109        dec.push(&encoded);
110        assert_eq!(dec.next_frame().unwrap(), Some(payload.to_vec()));
111        assert_eq!(dec.next_frame().unwrap(), None);
112    }
113
114    #[test]
115    fn reassembles_across_partial_pushes() {
116        let payload = b"hello";
117        let encoded = encode_frame(payload).unwrap();
118        let mut dec = FrameDecoder::new();
119        // Split mid-header then mid-body.
120        dec.push(&encoded[..2]);
121        assert_eq!(dec.next_frame().unwrap(), None);
122        dec.push(&encoded[2..6]);
123        assert_eq!(dec.next_frame().unwrap(), None);
124        dec.push(&encoded[6..]);
125        assert_eq!(dec.next_frame().unwrap(), Some(payload.to_vec()));
126    }
127
128    #[test]
129    fn decodes_two_frames_from_one_push() {
130        let mut buf = encode_frame(b"aa").unwrap();
131        buf.extend(encode_frame(b"bbb").unwrap());
132        let mut dec = FrameDecoder::new();
133        dec.push(&buf);
134        assert_eq!(dec.next_frame().unwrap(), Some(b"aa".to_vec()));
135        assert_eq!(dec.next_frame().unwrap(), Some(b"bbb".to_vec()));
136        assert_eq!(dec.next_frame().unwrap(), None);
137    }
138
139    #[test]
140    fn rejects_oversize_declared_length_without_allocating() {
141        // Declared length = u32::MAX, no body: must error, not buffer 4 GiB.
142        let mut dec = FrameDecoder::new();
143        dec.push(&u32::MAX.to_be_bytes());
144        assert_eq!(
145            dec.next_frame(),
146            Err(FrameError::TooLarge(u32::MAX as usize))
147        );
148    }
149
150    #[test]
151    fn stays_poisoned_after_oversize_no_error_loop() {
152        // After an oversize length the decoder is terminally poisoned — subsequent calls
153        // return Poisoned, not the same TooLarge in an infinite loop, and even valid bytes
154        // pushed afterward are ignored (the stream is unrecoverable).
155        let mut dec = FrameDecoder::new();
156        dec.push(&u32::MAX.to_be_bytes());
157        assert_eq!(
158            dec.next_frame(),
159            Err(FrameError::TooLarge(u32::MAX as usize))
160        );
161        assert_eq!(dec.next_frame(), Err(FrameError::Poisoned));
162        dec.push(&encode_frame(b"ok").unwrap());
163        assert_eq!(dec.next_frame(), Err(FrameError::Poisoned));
164    }
165
166    #[test]
167    fn encode_rejects_oversize_payload() {
168        let too_big = vec![0u8; MAX_FRAME_LEN + 1];
169        assert_eq!(
170            encode_frame(&too_big),
171            Err(FrameError::TooLarge(MAX_FRAME_LEN + 1))
172        );
173    }
174
175    #[test]
176    fn many_frames_in_one_push_all_decode_in_order() {
177        let mut buf = Vec::new();
178        for i in 0..1000u32 {
179            buf.extend(encode_frame(format!("f{i}").as_bytes()).unwrap());
180        }
181        let mut dec = FrameDecoder::new();
182        dec.push(&buf);
183        for i in 0..1000u32 {
184            assert_eq!(
185                dec.next_frame().unwrap(),
186                Some(format!("f{i}").into_bytes())
187            );
188        }
189        assert_eq!(dec.next_frame().unwrap(), None);
190    }
191}