Skip to main content

claude_codex/providers/cursor/
connect.rs

1use bytes::{Bytes, BytesMut};
2
3// Connect frame flags
4pub const FLAG_GZIP: u8 = 0x01;
5pub const FLAG_END: u8 = 0x02;
6
7/// A single Connect frame with flags and payload.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ConnectFrame {
10    pub flags: u8,
11    pub payload: Bytes,
12}
13
14/// Encode a payload into a Connect frame: 1 byte flags, 4 byte big-endian
15/// payload length, then the payload bytes.
16pub fn encode_connect_frame(payload: impl AsRef<[u8]>, flags: u8) -> Bytes {
17    let payload = payload.as_ref();
18    let mut out = BytesMut::with_capacity(5 + payload.len());
19    out.extend_from_slice(&[flags]);
20    out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
21    out.extend_from_slice(payload);
22    out.freeze()
23}
24
25/// Streaming decoder for Connect frames from a byte source.
26///
27/// Handles split chunks, multiple frames in a single chunk, and malformed
28/// (oversized) lengths. Does NOT handle gzip decompression inline -- the
29/// caller checks `FLAG_GZIP` and decompresses if desired.
30///
31/// End frames (FLAG_END set) with an empty or JSON payload are returned
32/// as ConnectFrames. The caller inspects the payload to determine whether
33/// it conveys a Connect error.
34#[derive(Default)]
35pub struct ConnectFrameDecoder {
36    buffer: BytesMut,
37}
38
39impl ConnectFrameDecoder {
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Feed bytes into the decoder. Returns all complete frames found.
45    ///
46    /// Returns an error if a frame header advertises a length that exceeds
47    /// `max_frame_payload` (default 64 MiB).
48    pub fn push(&mut self, chunk: impl AsRef<[u8]>) -> Result<Vec<ConnectFrame>, ConnectError> {
49        self.buffer.extend_from_slice(chunk.as_ref());
50        self.drain(64 * 1024 * 1024) // 64 MiB max payload
51    }
52
53    /// Same as `push` but with an explicit `max_payload` limit for testing.
54    pub fn push_with_limit(
55        &mut self,
56        chunk: impl AsRef<[u8]>,
57        max_payload: usize,
58    ) -> Result<Vec<ConnectFrame>, ConnectError> {
59        self.buffer.extend_from_slice(chunk.as_ref());
60        self.drain(max_payload)
61    }
62
63    fn drain(&mut self, max_payload: usize) -> Result<Vec<ConnectFrame>, ConnectError> {
64        let mut out = Vec::new();
65        loop {
66            if self.buffer.len() < 5 {
67                break;
68            }
69            let len = u32::from_be_bytes([
70                self.buffer[1],
71                self.buffer[2],
72                self.buffer[3],
73                self.buffer[4],
74            ]) as usize;
75
76            if len > max_payload {
77                return Err(ConnectError::PayloadTooLarge {
78                    length: len,
79                    max: max_payload,
80                });
81            }
82
83            if self.buffer.len() < 5 + len {
84                break;
85            }
86
87            let mut raw = self.buffer.split_to(5 + len);
88            out.push(ConnectFrame {
89                flags: raw[0],
90                payload: raw.split_off(5).freeze(),
91            });
92        }
93        Ok(out)
94    }
95
96    /// Return the number of buffered bytes (incomplete frame data).
97    pub fn buffered(&self) -> usize {
98        self.buffer.len()
99    }
100}
101
102/// Decode gzipped payload bytes. The caller decides when to call this based
103/// on frame flags & FLAG_GZIP.
104pub fn decode_gzip_frame(payload: &[u8]) -> Result<Vec<u8>, std::io::Error> {
105    use std::io::Read;
106    let mut decoder = flate2::read::GzDecoder::new(payload);
107    let mut out = Vec::new();
108    decoder.read_to_end(&mut out)?;
109    Ok(out)
110}
111
112/// Parse a Connect end-frame JSON error payload into a structured error.
113///
114/// Returns `None` if the payload is empty or not valid Connect error JSON.
115pub fn parse_connect_error(payload: &[u8]) -> Option<ConnectEndError> {
116    if payload.is_empty() {
117        return None;
118    }
119    let parsed: serde_json::Value = serde_json::from_slice(payload).ok()?;
120    let error = parsed.get("error")?;
121    let code = error.get("code")?.as_str()?;
122    let message = error.get("message")?.as_str().unwrap_or("Connect error");
123    let status = match code {
124        "resource_exhausted" => 429,
125        _ => 502,
126    };
127    Some(ConnectEndError {
128        code: code.to_string(),
129        message: message.to_string(),
130        detail: parsed.to_string(),
131        status,
132    })
133}
134
135#[derive(Debug, Clone)]
136pub struct ConnectEndError {
137    pub code: String,
138    pub message: String,
139    pub detail: String,
140    pub status: u16,
141}
142
143impl std::fmt::Display for ConnectEndError {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(
146            f,
147            "Connect error {}: {} ({})",
148            self.status, self.message, self.code
149        )
150    }
151}
152
153impl std::error::Error for ConnectEndError {}
154
155#[derive(Debug, Clone)]
156pub enum ConnectError {
157    PayloadTooLarge { length: usize, max: usize },
158}
159
160impl std::fmt::Display for ConnectError {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        match self {
163            ConnectError::PayloadTooLarge { length, max } => {
164                write!(f, "Connect frame payload {length} exceeds max {max}")
165            }
166        }
167    }
168}
169
170impl std::error::Error for ConnectError {}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn encode_roundtrip() {
178        let frame = encode_connect_frame(b"hello", 0);
179        let mut decoder = ConnectFrameDecoder::new();
180        let frames = decoder.push(&frame).unwrap();
181        assert_eq!(frames.len(), 1);
182        assert_eq!(frames[0].flags, 0);
183        assert_eq!(&frames[0].payload[..], b"hello");
184    }
185
186    #[test]
187    fn encode_with_gzip_flag() {
188        let frame = encode_connect_frame(b"gzip-data", FLAG_GZIP);
189        let mut decoder = ConnectFrameDecoder::new();
190        let frames = decoder.push(&frame).unwrap();
191        assert_eq!(frames.len(), 1);
192        assert_eq!(frames[0].flags, FLAG_GZIP);
193    }
194
195    #[test]
196    fn encode_with_end_flag() {
197        let frame = encode_connect_frame(b"", FLAG_END);
198        let mut decoder = ConnectFrameDecoder::new();
199        let frames = decoder.push(&frame).unwrap();
200        assert_eq!(frames.len(), 1);
201        assert_eq!(frames[0].flags, FLAG_END);
202        assert!(frames[0].payload.is_empty());
203    }
204
205    #[test]
206    fn encode_with_gzip_and_end_flags() {
207        let payload = b"end-data";
208        let frame = encode_connect_frame(payload, FLAG_GZIP | FLAG_END);
209        let mut decoder = ConnectFrameDecoder::new();
210        let frames = decoder.push(&frame).unwrap();
211        assert_eq!(frames.len(), 1);
212        assert_eq!(frames[0].flags, FLAG_GZIP | FLAG_END);
213        assert_eq!(&frames[0].payload[..], payload);
214    }
215
216    #[test]
217    fn multiple_frames_in_single_chunk() {
218        let f1 = encode_connect_frame(b"first", 0);
219        let f2 = encode_connect_frame(b"second", 0);
220        let mut combined = BytesMut::new();
221        combined.extend_from_slice(&f1);
222        combined.extend_from_slice(&f2);
223
224        let mut decoder = ConnectFrameDecoder::new();
225        let frames = decoder.push(combined).unwrap();
226        assert_eq!(frames.len(), 2);
227        assert_eq!(&frames[0].payload[..], b"first");
228        assert_eq!(&frames[1].payload[..], b"second");
229    }
230
231    #[test]
232    fn split_chunks_are_assembled() {
233        let frame = encode_connect_frame(b"split-test", 0);
234        let (a, b) = frame.split_at(3);
235
236        let mut decoder = ConnectFrameDecoder::new();
237        let frames = decoder.push(a).unwrap();
238        assert!(frames.is_empty());
239
240        let frames = decoder.push(b).unwrap();
241        assert_eq!(frames.len(), 1);
242        assert_eq!(&frames[0].payload[..], b"split-test");
243    }
244
245    #[test]
246    fn split_at_header_boundary() {
247        let frame = encode_connect_frame(b"split-at-5", 0);
248        // Split after the flags byte but before the length bytes are complete
249        let (a, b) = frame.split_at(1);
250
251        let mut decoder = ConnectFrameDecoder::new();
252        let frames = decoder.push(a).unwrap();
253        assert!(frames.is_empty());
254
255        let frames = decoder.push(b).unwrap();
256        assert_eq!(frames.len(), 1);
257        assert_eq!(&frames[0].payload[..], b"split-at-5");
258    }
259
260    #[test]
261    fn oversized_length_is_rejected() {
262        let mut decoder = ConnectFrameDecoder::new();
263        // Encode a frame with 1M payload (will exceed our 10-byte max)
264        let oversized = encode_connect_frame(vec![0u8; 100], 0);
265        let result = decoder.push_with_limit(&oversized, 10);
266        assert!(result.is_err());
267        match result.unwrap_err() {
268            ConnectError::PayloadTooLarge { length, max } => {
269                assert_eq!(length, 100);
270                assert_eq!(max, 10);
271            }
272        }
273    }
274
275    #[test]
276    fn empty_chunk_produces_no_frames() {
277        let mut decoder = ConnectFrameDecoder::new();
278        let frames = decoder.push(b"").unwrap();
279        assert!(frames.is_empty());
280    }
281
282    #[test]
283    fn buf_returns_buffered_bytes() {
284        let mut decoder = ConnectFrameDecoder::new();
285        // Push part of a frame header
286        decoder.push(b"\x00\x00").unwrap();
287        assert_eq!(decoder.buffered(), 2);
288    }
289
290    #[test]
291    fn clean_end_frame_empty_payload() {
292        let frame = encode_connect_frame(b"", FLAG_END);
293        let mut decoder = ConnectFrameDecoder::new();
294        let frames = decoder.push(frame).unwrap();
295        assert_eq!(frames.len(), 1);
296        assert_eq!(frames[0].flags, FLAG_END);
297        assert!(frames[0].payload.is_empty());
298        // Parse error from empty payload
299        assert!(parse_connect_error(&frames[0].payload).is_none());
300    }
301
302    #[test]
303    fn connect_json_error_parsing() {
304        let json_err = serde_json::json!({
305            "error": {
306                "code": "resource_exhausted",
307                "message": "quota exceeded",
308                "details": []
309            }
310        });
311        let payload = serde_json::to_vec(&json_err).unwrap();
312        let frame = encode_connect_frame(&payload, FLAG_END);
313        let mut decoder = ConnectFrameDecoder::new();
314        let frames = decoder.push(frame).unwrap();
315        assert_eq!(frames.len(), 1);
316
317        let err = parse_connect_error(&frames[0].payload).unwrap();
318        assert_eq!(err.code, "resource_exhausted");
319        assert_eq!(err.status, 429);
320        assert_eq!(err.message, "quota exceeded");
321    }
322
323    #[test]
324    fn connect_json_unavailable_error() {
325        let json_err = serde_json::json!({
326            "error": {
327                "code": "unavailable",
328                "message": "service unavailable"
329            }
330        });
331        let payload = serde_json::to_vec(&json_err).unwrap();
332        let err = parse_connect_error(&payload).unwrap();
333        assert_eq!(err.code, "unavailable");
334        assert_eq!(err.status, 502);
335    }
336
337    #[test]
338    fn frame_fixture_matches_reference_layout() {
339        // Connect frame: flags=0x00, length=3 (0x00000003), payload="abc"
340        // Wire format: [0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63]
341        let frame = encode_connect_frame(b"abc", 0);
342        assert_eq!(hex::encode(frame), "0000000003616263");
343    }
344
345    #[test]
346    fn frame_fixture_with_flags() {
347        // flags=0x01, length=3
348        let frame = encode_connect_frame(b"xyz", 0x01);
349        assert_eq!(hex::encode(frame), "010000000378797a");
350    }
351
352    #[test]
353    fn gzip_frame_decompress() {
354        let payload = b"hello gzip";
355        let mut compressed = Vec::new();
356        {
357            use std::io::Write;
358            let mut encoder =
359                flate2::write::GzEncoder::new(&mut compressed, flate2::Compression::fast());
360            encoder.write_all(payload).unwrap();
361            encoder.finish().unwrap();
362        }
363
364        let frame = encode_connect_frame(&compressed, FLAG_GZIP);
365        let mut decoder = ConnectFrameDecoder::new();
366        let frames = decoder.push(frame).unwrap();
367        assert_eq!(frames.len(), 1);
368        assert_eq!(frames[0].flags, FLAG_GZIP);
369
370        let decompressed = decode_gzip_frame(&frames[0].payload).unwrap();
371        assert_eq!(decompressed, b"hello gzip");
372    }
373}