1use std::io::{self, BufRead, Read, Write};
6
7use serde::Deserialize;
8use serde_json::Value;
9
10use crate::{EventKind, MAX_CAPTURE_BYTES, MAX_RECORD_BYTES, SCHEMA_VERSION};
11
12#[derive(Deserialize)]
14pub struct Row {
15 pub schema_version: u32,
16 pub seq: u64,
17 pub event: EventKind,
18 pub fields: Value,
19}
20
21const LINE_LIMIT: usize = MAX_RECORD_BYTES + 1024;
24const TOTAL_LIMIT: usize = MAX_CAPTURE_BYTES;
27
28pub fn scan(
32 mut input: impl BufRead,
33 mut visit: impl FnMut(Row) -> io::Result<()>,
34) -> io::Result<bool> {
35 let (mut total, mut seq, mut ended, mut complete) = (0usize, 1u64, false, false);
36 loop {
37 let mut bytes = Vec::new();
38 let read = Read::by_ref(&mut input)
39 .take(LINE_LIMIT as u64 + 1)
40 .read_until(b'\n', &mut bytes)?;
41 if read == 0 {
42 break;
43 }
44 total = total
45 .checked_add(read)
46 .ok_or_else(|| io::Error::other("input limit"))?;
47 if read > LINE_LIMIT || total > TOTAL_LIMIT || bytes.last() != Some(&b'\n') {
48 return Err(io::Error::other("trace truncated or exceeds input limit"));
49 }
50 if ended {
51 return Err(io::Error::other("records after trace terminal"));
52 }
53 let row: Row =
54 serde_json::from_slice(&bytes).map_err(|_| io::Error::other("invalid trace record"))?;
55 if row.schema_version != SCHEMA_VERSION || row.seq != seq {
56 return Err(io::Error::other("unsupported schema or missing sequence"));
57 }
58 seq = seq
59 .checked_add(1)
60 .ok_or_else(|| io::Error::other("sequence overflow"))?;
61 if row.event == EventKind::TraceEnd {
62 ended = true;
63 complete = row.fields["complete"] == true;
64 }
65 visit(row)?;
66 }
67 Ok(ended && complete)
68}
69
70pub fn metadata(input: impl BufRead, mut out: impl Write) -> io::Result<()> {
77 writeln!(
78 out,
79 "{{\"schema\":\"strop-metadata-export-v1\",\"replayable\":false}}"
80 )?;
81 let complete = scan(input, |row| {
82 serde_json::to_writer(
83 &mut out,
84 &serde_json::json!({"seq": row.seq, "category": row.event}),
85 )?;
86 out.write_all(b"\n")
87 })?;
88 serde_json::to_writer(
89 &mut out,
90 &serde_json::json!({"export_end":true,"source_complete":complete,"replayable":false}),
91 )?;
92 out.write_all(b"\n")
93}
94
95pub fn replay_nodes(input: impl BufRead) -> io::Result<Vec<crate::replay::Node>> {
99 let mut nodes = Vec::new();
100 let mut full = false;
101 let complete = scan(input, |row| {
102 if row.seq == 1 {
103 full = row.event == EventKind::SessionStart && row.fields["full_content"] == true;
104 }
105 if row.event == EventKind::Replay {
106 nodes.push(
107 serde_json::from_value(row.fields)
108 .map_err(|_| io::Error::other("invalid forensic record"))?,
109 );
110 }
111 Ok(())
112 })?;
113 if !full || !complete {
114 return Err(io::Error::other(
115 "full replay requires complete full-content capture",
116 ));
117 }
118 if !matches!(nodes.first(), Some(crate::replay::Node::Seed { .. }))
119 || !matches!(nodes.last(), Some(crate::replay::Node::End))
120 {
121 return Err(io::Error::other("missing forensic seed or end"));
122 }
123 Ok(nodes)
124}