1use std::io::{self, Read, Write};
6
7use arrow::array::RecordBatch;
8use arrow::datatypes::SchemaRef;
9use arrow::ipc::reader::StreamReader;
10use arrow::ipc::writer::StreamWriter;
11
12pub const JSON_TAG: u8 = b'J';
13pub const ARROW_TAG: u8 = b'A';
14pub const MAX_FRAME: u32 = 1 << 30;
16
17pub enum Frame {
18 Json(serde_json::Value),
19 Arrow(Vec<u8>),
20}
21
22impl std::fmt::Debug for Frame {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Frame::Json(v) => write!(f, "Json({v})"),
27 Frame::Arrow(b) => write!(f, "Arrow frame ({} bytes)", b.len()),
28 }
29 }
30}
31
32#[derive(Debug)]
33pub enum FrameError {
34 Eof,
36 Io(io::Error),
37 Malformed(String),
38}
39
40impl std::fmt::Display for FrameError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 FrameError::Eof => f.write_str("end of stream"),
44 FrameError::Io(e) => write!(f, "I/O error: {e}"),
45 FrameError::Malformed(m) => write!(f, "malformed frame: {m}"),
46 }
47 }
48}
49
50impl std::error::Error for FrameError {}
51
52pub fn read_frame(r: &mut impl Read) -> Result<Frame, FrameError> {
53 let mut len = [0u8; 4];
54 match r.read(&mut len[..1]) {
55 Ok(0) => return Err(FrameError::Eof),
56 Ok(_) => {}
57 Err(e) => return Err(FrameError::Io(e)),
58 }
59 r.read_exact(&mut len[1..])
60 .map_err(|_| FrameError::Malformed("stream ended inside a frame header".into()))?;
61 let len = u32::from_be_bytes(len);
62 if len == 0 || len > MAX_FRAME {
63 return Err(FrameError::Malformed(format!(
64 "frame length {len} is out of range"
65 )));
66 }
67 let mut body = vec![0u8; len as usize];
68 r.read_exact(&mut body)
69 .map_err(|_| FrameError::Malformed(format!("stream ended inside a {len}-byte frame")))?;
70 match body.split_first() {
71 Some((&JSON_TAG, json)) => serde_json::from_slice(json)
72 .map(Frame::Json)
73 .map_err(|e| FrameError::Malformed(format!("invalid JSON: {e}"))),
74 Some((&ARROW_TAG, _)) => {
75 body.remove(0);
76 Ok(Frame::Arrow(body))
77 }
78 Some((t, _)) => Err(FrameError::Malformed(format!(
79 "unknown frame type byte 0x{t:02x}"
80 ))),
81 None => Err(FrameError::Malformed("empty frame".into())),
82 }
83}
84
85pub fn write_json(w: &mut impl Write, v: &impl serde::Serialize) -> io::Result<()> {
86 let mut body = vec![JSON_TAG];
87 serde_json::to_writer(&mut body, v)?;
88 write_body(w, &body)
89}
90
91pub fn write_arrow(w: &mut impl Write, ipc: &[u8]) -> io::Result<()> {
92 let mut body = Vec::with_capacity(ipc.len() + 1);
93 body.push(ARROW_TAG);
94 body.extend_from_slice(ipc);
95 write_body(w, &body)
96}
97
98fn write_body(w: &mut impl Write, body: &[u8]) -> io::Result<()> {
99 let len = u32::try_from(body.len())
100 .ok()
101 .filter(|l| *l <= MAX_FRAME)
102 .ok_or_else(|| io::Error::other("frame too large"))?;
103 w.write_all(&len.to_be_bytes())?;
104 w.write_all(body)?;
105 w.flush()
106}
107
108pub fn encode_batch(batch: &RecordBatch) -> Result<Vec<u8>, arrow::error::ArrowError> {
110 let mut buf = Vec::new();
111 {
112 let mut w = StreamWriter::try_new(&mut buf, &batch.schema())?;
113 w.write(batch)?;
114 w.finish()?;
115 }
116 Ok(buf)
117}
118
119pub fn decode_batches(ipc: &[u8]) -> Result<(SchemaRef, Vec<RecordBatch>), arrow::error::ArrowError> {
121 let reader = StreamReader::try_new(io::Cursor::new(ipc), None)?;
122 let schema = reader.schema();
123 let batches = reader.collect::<Result<Vec<_>, _>>()?;
124 Ok((schema, batches))
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use arrow::array::Int64Array;
131 use std::sync::Arc;
132
133 #[test]
134 fn json_and_arrow_frames_round_trip() {
135 let batch = RecordBatch::try_from_iter([("n", Arc::new(Int64Array::from(vec![1, 2])) as _)]).unwrap();
136 let mut buf = Vec::new();
137 write_json(&mut buf, &serde_json::json!({"type": "ok"})).unwrap();
138 write_arrow(&mut buf, &encode_batch(&batch).unwrap()).unwrap();
139 let mut r = io::Cursor::new(buf);
140 assert!(matches!(read_frame(&mut r).unwrap(), Frame::Json(v) if v["type"] == "ok"));
141 let Frame::Arrow(ipc) = read_frame(&mut r).unwrap() else {
142 panic!()
143 };
144 let (_, batches) = decode_batches(&ipc).unwrap();
145 assert_eq!(batches, vec![batch]);
146 assert!(matches!(read_frame(&mut r), Err(FrameError::Eof)));
147 }
148
149 #[test]
150 fn rejects_malformed_frames() {
151 for bad in [
152 vec![0, 0, 0, 0],
153 vec![0, 0, 0, 2, b'X', 1],
154 vec![0, 0, 0, 9, b'J'],
155 vec![0, 0],
156 ] {
157 assert!(matches!(
158 read_frame(&mut io::Cursor::new(bad)),
159 Err(FrameError::Malformed(_))
160 ));
161 }
162 }
163}