1use anyhow::{bail, ensure, Context, Result};
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Number, Value};
5use std::io::{Read, Write};
6pub const MAX_FRAME_BYTES: usize = 256 * 1024;
7pub const MAX_RECORDS: usize = 256;
8pub const MAX_DEPTH: usize = 32;
9pub const MAX_VALUES: usize = 16384;
10pub const DEFAULT_LLM_FALLBACK: bool = false;
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct Timestamp {
14 pub seconds: u64,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub nanoseconds: Option<u32>,
17}
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct Entry {
21 pub timestamp: Timestamp,
22 pub record: Map<String, Value>,
23}
24#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
25#[serde(deny_unknown_fields)]
26pub struct Batch {
27 pub tag: String,
28 pub entries: Vec<Entry>,
29 #[serde(default = "default_mode")]
30 pub mode: String,
31 #[serde(default)]
32 pub require_ack: bool,
33 #[serde(skip)]
34 pub chunk: Option<String>,
35}
36fn default_mode() -> String {
37 "forward".into()
38}
39#[derive(Clone, Debug, PartialEq)]
40pub enum Node {
41 Nil,
42 Bool(bool),
43 Int(i64),
44 Uint(u64),
45 Float(f64),
46 Str(Vec<u8>),
47 Bin(Vec<u8>),
48 Array(Vec<Node>),
49 Map(Vec<(Node, Node)>),
50 Ext(i8, Vec<u8>),
51}
52impl Node {
53 pub fn string(&self) -> Result<&str> {
54 match self {
55 Self::Str(b) => Ok(std::str::from_utf8(b)?),
56 _ => bail!("expected MessagePack string"),
57 }
58 }
59 fn json(self) -> Result<Value> {
60 Ok(match self {
61 Self::Nil => Value::Null,
62 Self::Bool(b) => Value::Bool(b),
63 Self::Int(i) => Value::Number(i.into()),
64 Self::Uint(i) => Value::Number(i.into()),
65 Self::Float(f) => Value::Number(Number::from_f64(f).context("nonfinite record value")?),
66 Self::Str(b) => Value::String(String::from_utf8(b)?),
67 Self::Array(a) => Value::Array(a.into_iter().map(Self::json).collect::<Result<_>>()?),
68 Self::Map(pairs) => {
69 let mut out = Map::new();
70 for (k, v) in pairs {
71 let k = k.string()?.to_owned();
72 ensure!(out.insert(k, v.json()?).is_none(), "duplicate record key");
73 }
74 Value::Object(out)
75 }
76 _ => bail!("binary/extension record values are outside the typed JSON surface"),
77 })
78 }
79 fn uint(&self) -> Result<u64> {
80 match self {
81 Self::Uint(n) => Ok(*n),
82 Self::Int(n) => Ok(u64::try_from(*n)?),
83 _ => bail!("expected nonnegative integer"),
84 }
85 }
86}
87#[derive(Debug)]
88enum ParseError {
89 Short,
90 Bad(&'static str),
91}
92fn take<'a>(b: &'a [u8], at: &mut usize, n: usize) -> std::result::Result<&'a [u8], ParseError> {
93 let end = at
94 .checked_add(n)
95 .ok_or(ParseError::Bad("length overflow"))?;
96 let out = b.get(*at..end).ok_or(ParseError::Short)?;
97 *at = end;
98 Ok(out)
99}
100fn uint(b: &[u8], at: &mut usize, n: usize) -> std::result::Result<u64, ParseError> {
101 let mut out = 0;
102 for v in take(b, at, n)? {
103 out = out * 256 + u64::from(*v);
104 }
105 Ok(out)
106}
107fn len(b: &[u8], at: &mut usize, n: usize) -> std::result::Result<usize, ParseError> {
108 let n = uint(b, at, n)? as usize;
109 if n > MAX_FRAME_BYTES {
110 return Err(ParseError::Bad("declared length exceeds frame limit"));
111 }
112 Ok(n)
113}
114fn parse(
115 b: &[u8],
116 at: &mut usize,
117 depth: usize,
118 budget: &mut usize,
119) -> std::result::Result<Node, ParseError> {
120 if depth > MAX_DEPTH {
121 return Err(ParseError::Bad("MessagePack nesting limit"));
122 }
123 if *budget == 0 {
124 return Err(ParseError::Bad("MessagePack value count limit"));
125 }
126 *budget -= 1;
127 let m = take(b, at, 1)?[0];
128 let (kind, count) = match m {
129 0x00..=0x7f => return Ok(Node::Uint(m as u64)),
130 0xe0..=0xff => return Ok(Node::Int(m as i8 as i64)),
131 0xc0 => return Ok(Node::Nil),
132 0xc2..=0xc3 => return Ok(Node::Bool(m == 0xc3)),
133 0xcc..=0xcf => return Ok(Node::Uint(uint(b, at, 1usize << (m - 0xcc))?)),
134 0xd0..=0xd3 => {
135 let size = 1usize << (m - 0xd0);
136 let n = uint(b, at, size)?;
137 return Ok(Node::Int(match size {
138 1 => n as i8 as i64,
139 2 => n as i16 as i64,
140 4 => n as i32 as i64,
141 _ => n as i64,
142 }));
143 }
144 0xca => return Ok(Node::Float(f32::from_bits(uint(b, at, 4)? as u32) as f64)),
145 0xcb => return Ok(Node::Float(f64::from_bits(uint(b, at, 8)?))),
146 0xa0..=0xbf => (0, (m & 31) as usize),
147 0xd9 => (0, len(b, at, 1)?),
148 0xda => (0, len(b, at, 2)?),
149 0xdb => (0, len(b, at, 4)?),
150 0xc4 => (1, len(b, at, 1)?),
151 0xc5 => (1, len(b, at, 2)?),
152 0xc6 => (1, len(b, at, 4)?),
153 0x90..=0x9f => (2, (m & 15) as usize),
154 0xdc => (2, len(b, at, 2)?),
155 0xdd => (2, len(b, at, 4)?),
156 0x80..=0x8f => (3, (m & 15) as usize),
157 0xde => (3, len(b, at, 2)?),
158 0xdf => (3, len(b, at, 4)?),
159 0xd4..=0xd8 => (4, 1usize << (m - 0xd4)),
160 0xc7 => (4, len(b, at, 1)?),
161 0xc8 => (4, len(b, at, 2)?),
162 0xc9 => (4, len(b, at, 4)?),
163 _ => return Err(ParseError::Bad("invalid MessagePack marker")),
164 };
165 Ok(match kind {
166 0 => Node::Str(take(b, at, count)?.to_vec()),
167 1 => Node::Bin(take(b, at, count)?.to_vec()),
168 2 => {
169 if count > *budget {
170 return Err(ParseError::Bad("declared array count limit"));
171 }
172 let mut out = Vec::new();
173 for _ in 0..count {
174 out.push(parse(b, at, depth + 1, budget)?);
175 }
176 Node::Array(out)
177 }
178 3 => {
179 if count > *budget / 2 {
180 return Err(ParseError::Bad("declared map count limit"));
181 }
182 let mut out = Vec::new();
183 for _ in 0..count {
184 let k = parse(b, at, depth + 1, budget)?;
185 let v = parse(b, at, depth + 1, budget)?;
186 out.push((k, v));
187 }
188 Node::Map(out)
189 }
190 _ => {
191 let t = take(b, at, 1)?[0] as i8;
192 Node::Ext(t, take(b, at, count)?.to_vec())
193 }
194 })
195}
196pub fn parse_one(b: &[u8]) -> Result<Option<(Node, usize)>> {
197 let mut at = 0;
198 match parse(b, &mut at, 0, &mut MAX_VALUES.clone()) {
199 Ok(n) => {
200 ensure!(at <= MAX_FRAME_BYTES, "MessagePack frame byte limit");
201 Ok(Some((n, at)))
202 }
203 Err(ParseError::Short) => {
204 ensure!(
205 b.len() <= MAX_FRAME_BYTES,
206 "incomplete MessagePack frame byte limit"
207 );
208 Ok(None)
209 }
210 Err(ParseError::Bad(message)) => bail!(message),
211 }
212}
213#[derive(Default)]
214pub struct Decoder {
215 bytes: Vec<u8>,
216}
217impl Decoder {
218 pub fn feed(&mut self, b: &[u8]) -> Result<()> {
219 ensure!(
220 self.bytes.len() + b.len() <= MAX_FRAME_BYTES + 8192,
221 "buffered byte limit"
222 );
223 self.bytes.extend_from_slice(b);
224 Ok(())
225 }
226 pub fn next_node(&mut self) -> Result<Option<Node>> {
227 if self.bytes.is_empty() {
228 return Ok(None);
229 }
230 if let Some((n, len)) = parse_one(&self.bytes)? {
231 self.bytes.drain(..len);
232 Ok(Some(n))
233 } else {
234 Ok(None)
235 }
236 }
237 pub fn finish(&self) -> Result<()> {
238 ensure!(self.bytes.is_empty(), "EOF in MessagePack frame");
239 Ok(())
240 }
241}
242fn strnode(s: &str) -> Node {
243 Node::Str(s.as_bytes().to_vec())
244}
245fn jsonnode(v: &Value, depth: usize, budget: &mut usize) -> Result<Node> {
246 ensure!(
247 depth <= MAX_DEPTH && *budget > 0,
248 "record nesting/value count limit"
249 );
250 *budget -= 1;
251 Ok(match v {
252 Value::Null => Node::Nil,
253 Value::Bool(b) => Node::Bool(*b),
254 Value::Number(n) => {
255 if let Some(n) = n.as_u64() {
256 Node::Uint(n)
257 } else if let Some(n) = n.as_i64() {
258 Node::Int(n)
259 } else {
260 let n = n.as_f64().context("invalid number")?;
261 ensure!(n.is_finite(), "nonfinite number");
262 Node::Float(n)
263 }
264 }
265 Value::String(s) => strnode(s),
266 Value::Array(a) => Node::Array(
267 a.iter()
268 .map(|v| jsonnode(v, depth + 1, budget))
269 .collect::<Result<_>>()?,
270 ),
271 Value::Object(map) => Node::Map(
272 map.iter()
273 .map(|(k, v)| {
274 ensure!(*budget > 0, "record value count limit");
275 *budget -= 1;
276 Ok((strnode(k), jsonnode(v, depth + 1, budget)?))
277 })
278 .collect::<Result<_>>()?,
279 ),
280 })
281}
282fn header(
283 out: &mut Vec<u8>,
284 small: Option<(u8, usize)>,
285 m8: Option<u8>,
286 m16: u8,
287 m32: u8,
288 len: usize,
289) {
290 if let Some((m, max)) = small {
291 if len < max {
292 out.push(m | len as u8);
293 return;
294 }
295 }
296 if let Some(m) = m8 {
297 if len <= 255 {
298 out.extend_from_slice(&[m, len as u8]);
299 return;
300 }
301 }
302 if len <= 65535 {
303 out.push(m16);
304 out.extend_from_slice(&(len as u16).to_be_bytes());
305 } else {
306 out.push(m32);
307 out.extend_from_slice(&(len as u32).to_be_bytes());
308 }
309}
310fn emit(n: &Node, out: &mut Vec<u8>) -> Result<()> {
311 match n {
312 Node::Nil => out.push(0xc0),
313 Node::Bool(b) => out.push(if *b { 0xc3 } else { 0xc2 }),
314 Node::Uint(n) => {
315 if *n < 128 {
316 out.push(*n as u8)
317 } else {
318 out.push(0xcf);
319 out.extend_from_slice(&n.to_be_bytes())
320 }
321 }
322 Node::Int(n) => {
323 out.push(0xd3);
324 out.extend_from_slice(&n.to_be_bytes())
325 }
326 Node::Float(n) => {
327 ensure!(n.is_finite(), "nonfinite value");
328 out.push(0xcb);
329 out.extend_from_slice(&n.to_bits().to_be_bytes())
330 }
331 Node::Str(b) => {
332 header(out, Some((0xa0, 32)), Some(0xd9), 0xda, 0xdb, b.len());
333 out.extend_from_slice(b)
334 }
335 Node::Bin(b) => {
336 header(out, None, Some(0xc4), 0xc5, 0xc6, b.len());
337 out.extend_from_slice(b)
338 }
339 Node::Array(a) => {
340 header(out, Some((0x90, 16)), None, 0xdc, 0xdd, a.len());
341 for v in a {
342 emit(v, out)?;
343 }
344 }
345 Node::Map(a) => {
346 header(out, Some((0x80, 16)), None, 0xde, 0xdf, a.len());
347 for (k, v) in a {
348 emit(k, out)?;
349 emit(v, out)?;
350 }
351 }
352 Node::Ext(t, b) => {
353 ensure!(b.len() == 8, "only EventTime ext8 supported");
354 out.extend_from_slice(&[0xd7, *t as u8]);
355 out.extend_from_slice(b)
356 }
357 }
358 ensure!(out.len() <= MAX_FRAME_BYTES, "encoded frame byte limit");
359 Ok(())
360}
361pub fn encode_node(n: &Node) -> Result<Vec<u8>> {
362 let mut out = Vec::new();
363 emit(n, &mut out)?;
364 Ok(out)
365}
366fn timestamp(n: &Node) -> Result<Timestamp> {
367 match n {
368 Node::Ext(0, b) if b.len() == 8 => {
369 let seconds = u32::from_be_bytes(b[..4].try_into()?) as u64;
370 let nano = u32::from_be_bytes(b[4..].try_into()?);
371 ensure!(nano < 1_000_000_000, "EventTime nanoseconds out of range");
372 Ok(Timestamp {
373 seconds,
374 nanoseconds: Some(nano),
375 })
376 }
377 _ => Ok(Timestamp {
378 seconds: n.uint()?,
379 nanoseconds: None,
380 }),
381 }
382}
383fn timestampnode(ts: &Timestamp) -> Result<Node> {
384 if let Some(nano) = ts.nanoseconds {
385 ensure!(nano < 1_000_000_000, "nanoseconds must be <1e9");
386 let seconds = u32::try_from(ts.seconds).context("EventTime seconds exceed uint32")?;
387 let mut b = seconds.to_be_bytes().to_vec();
388 b.extend_from_slice(&nano.to_be_bytes());
389 Ok(Node::Ext(0, b))
390 } else {
391 Ok(Node::Uint(ts.seconds))
392 }
393}
394fn entry(n: Node) -> Result<Entry> {
395 let Node::Array(mut a) = n else {
396 bail!("entry must be array")
397 };
398 ensure!(a.len() == 2, "entry must contain timestamp and record");
399 let record = a
400 .pop()
401 .unwrap()
402 .json()?
403 .as_object()
404 .context("record must be map")?
405 .clone();
406 Ok(Entry {
407 timestamp: timestamp(&a[0])?,
408 record,
409 })
410}
411pub fn parse_batch(n: Node) -> Result<Option<Batch>> {
412 if n == Node::Nil {
413 return Ok(None);
414 }
415 let Node::Array(a) = n else {
416 bail!("Forward frame must be an array")
417 };
418 ensure!(a.len() >= 2 && a.len() <= 4, "invalid Forward frame arity");
419 let tag = a[0].string()?.to_owned();
420 ensure!(!tag.is_empty() && tag.len() <= 1024, "tag byte limit");
421 let single = matches!(&a[1], Node::Uint(_) | Node::Int(_) | Node::Ext(_, _));
422 let expected = if single { 3 } else { 2 };
423 ensure!(
424 a.len() == expected || a.len() == expected + 1,
425 "invalid mode arity"
426 );
427 let options = if a.len() == expected + 1 {
428 a.last()
429 .unwrap()
430 .clone()
431 .json()?
432 .as_object()
433 .context("options must be a map")?
434 .clone()
435 } else {
436 Map::new()
437 };
438 let chunk = options
439 .get("chunk")
440 .map(|v| {
441 let s = v.as_str().context("chunk must be string")?;
442 ensure!(
443 !s.is_empty() && s.len() <= 256 && s.bytes().all(|b| b.is_ascii_graphic()),
444 "chunk token byte limit"
445 );
446 Ok::<_, anyhow::Error>(s.to_owned())
447 })
448 .transpose()?;
449 let mut mode = "message";
450 let mut entries = if single {
451 vec![Entry {
452 timestamp: timestamp(&a[1])?,
453 record: a[2]
454 .clone()
455 .json()?
456 .as_object()
457 .context("record must be map")?
458 .clone(),
459 }]
460 } else {
461 match &a[1] {
462 Node::Array(records) => {
463 mode = "forward";
464 ensure!(records.len() <= MAX_RECORDS, "record count limit");
465 records
466 .clone()
467 .into_iter()
468 .map(entry)
469 .collect::<Result<Vec<_>>>()?
470 }
471 Node::Bin(bytes) | Node::Str(bytes) => {
472 mode = "packed";
473 let mut bytes = bytes.clone();
474 if let Some(compression) = options.get("compressed").filter(|v| *v != "text") {
475 ensure!(compression == "gzip", "unsupported packed compression");
476 ensure!(
477 matches!(&a[1], Node::Bin(_)),
478 "compressed packed entries must be bin"
479 );
480 mode = "compressed_packed";
481 let mut reader = flate2::bufread::MultiGzDecoder::new(bytes.as_slice());
482 let mut out = Vec::new();
483 reader
484 .by_ref()
485 .take((MAX_FRAME_BYTES + 1) as u64)
486 .read_to_end(&mut out)?;
487 ensure!(
488 out.len() <= MAX_FRAME_BYTES && reader.get_ref().is_empty(),
489 "packed decompression byte limit/trailing data"
490 );
491 bytes = out;
492 }
493 let mut out = Vec::new();
494 let mut at = 0;
495 let mut budget = MAX_VALUES;
496 while at < bytes.len() {
497 ensure!(out.len() < MAX_RECORDS, "record count limit");
498 let node = parse(&bytes, &mut at, 0, &mut budget).map_err(|e| match e {
499 ParseError::Short => anyhow::anyhow!("incomplete packed entry"),
500 ParseError::Bad(message) => anyhow::anyhow!(message),
501 })?;
502 out.push(entry(node)?);
503 }
504 out
505 }
506 _ => bail!("invalid Forward carrier"),
507 }
508 };
509 ensure!(
510 !entries.is_empty() && entries.len() <= MAX_RECORDS,
511 "batch must have 1..256 entries"
512 );
513 if let Some(size) = options.get("size") {
514 ensure!(
515 size.as_u64() == Some(entries.len() as u64),
516 "size option disagrees with entries"
517 );
518 }
519 if single || mode == "forward" {
520 ensure!(
521 !options.contains_key("compressed"),
522 "compression requires PackedForward"
523 );
524 }
525 let mut budget = MAX_VALUES;
527 for e in &entries {
528 jsonnode(&Value::Object(e.record.clone()), 0, &mut budget)?;
529 }
530 Ok(Some(Batch {
531 tag,
532 entries: std::mem::take(&mut entries),
533 mode: mode.into(),
534 require_ack: chunk.is_some(),
535 chunk,
536 }))
537}
538pub fn encode_batch(b: &Batch, chunk: Option<&str>) -> Result<Vec<u8>> {
539 ensure!(!b.tag.is_empty() && b.tag.len() <= 1024, "tag byte limit");
540 ensure!(
541 !b.entries.is_empty() && b.entries.len() <= MAX_RECORDS,
542 "batch must contain 1..256 entries"
543 );
544 let mut budget = MAX_VALUES;
545 let entries = b
546 .entries
547 .iter()
548 .map(|e| {
549 Ok(Node::Array(vec![
550 timestampnode(&e.timestamp)?,
551 jsonnode(&Value::Object(e.record.clone()), 0, &mut budget)?,
552 ]))
553 })
554 .collect::<Result<Vec<_>>>()?;
555 let mut options = vec![(strnode("size"), Node::Uint(entries.len() as u64))];
556 if let Some(chunk) = chunk {
557 ensure!(
558 !chunk.is_empty() && chunk.len() <= 256 && chunk.bytes().all(|b| b.is_ascii_graphic()),
559 "chunk token byte limit"
560 );
561 options.push((strnode("chunk"), strnode(chunk)));
562 }
563 let mut root = vec![strnode(&b.tag)];
564 match b.mode.as_str() {
565 "message" => {
566 ensure!(entries.len() == 1, "message mode requires one entry");
567 let Node::Array(a) = &entries[0] else {
568 unreachable!()
569 };
570 root.extend(a.clone());
571 }
572 "forward" => root.push(Node::Array(entries)),
573 "packed" | "compressed_packed" => {
574 let mut bytes = Vec::new();
575 for e in entries {
576 emit(&e, &mut bytes)?;
577 }
578 if b.mode == "compressed_packed" {
579 let mut encoder =
580 flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
581 encoder.write_all(&bytes)?;
582 bytes = encoder.finish()?;
583 options.push((strnode("compressed"), strnode("gzip")));
584 }
585 root.push(Node::Bin(bytes));
586 }
587 _ => bail!("mode must be message, forward, packed or compressed_packed"),
588 }
589 root.push(Node::Map(options));
590 let bytes = encode_node(&Node::Array(root))?;
591 let (node, _) = parse_one(&bytes)?.context("incomplete encoded frame")?;
592 parse_batch(node)?.context("encoded batch unexpectedly empty")?;
595 Ok(bytes)
596}
597pub fn encode_ack(chunk: &str) -> Result<Vec<u8>> {
598 encode_node(&Node::Map(vec![(strnode("ack"), strnode(chunk))]))
599}
600pub fn parse_ack(n: Node) -> Result<String> {
601 let value = n.json()?;
602 let map = value.as_object().context("ACK must be map")?;
603 ensure!(map.len() == 1, "unexpected ACK fields");
604 Ok(map
605 .get("ack")
606 .and_then(Value::as_str)
607 .context("ACK string missing")?
608 .to_owned())
609}