1use std::borrow::Cow;
17
18use kernel::consts_external::{LogVersion, readable_log_v};
19use kernel::ledger::chain_hash;
20use kernel::{AxCode, AxError, B3Hash, EventKind, EventRecord, GENESIS_PREV, Seq};
21use serde::Deserialize;
22use serde::de::IntoDeserializer;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct LineCheck {
27 prev: B3Hash,
28 expected: Seq,
29}
30
31#[derive(Debug, Clone, PartialEq)]
34pub enum CheckedLine {
35 Known(EventRecord),
36 IgnoredUnknown(Seq),
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub enum LineFault {
42 NotALine(String),
44 VersionAhead(u64),
46 NotAVersion(u64),
48 ChainBreak,
50 SeqGap { found: Seq, expected: Seq },
52 UnknownKind(String),
54 NotCanonical(String),
57 SeqExhausted(AxError),
59}
60
61#[derive(Deserialize)]
64struct Envelope<'a> {
65 v: u64,
66 seq: Seq,
67 prev: B3Hash,
68 #[serde(borrow)]
69 kind: Cow<'a, str>,
70 #[serde(default)]
71 ig: bool,
72}
73
74impl LineCheck {
75 pub fn at_genesis() -> Self {
77 Self::after(GENESIS_PREV, Seq::FIRST)
78 }
79
80 pub(crate) fn after(prev: B3Hash, expected: Seq) -> Self {
82 Self { prev, expected }
83 }
84
85 pub fn expected(&self) -> Seq {
87 self.expected
88 }
89
90 pub(crate) fn prev(&self) -> B3Hash {
91 self.prev
92 }
93
94 pub(crate) fn carries_envelope(raw: &[u8]) -> bool {
97 serde_json::from_slice::<Envelope>(raw).is_ok()
98 }
99
100 pub fn advance(&mut self, raw: &[u8]) -> Result<CheckedLine, LineFault> {
103 let envelope = readable_envelope(raw)?;
104 if envelope.prev != self.prev {
105 return Err(LineFault::ChainBreak);
106 }
107 if envelope.seq != self.expected {
108 return Err(LineFault::SeqGap {
109 found: envelope.seq,
110 expected: self.expected,
111 });
112 }
113 let checked = classify(envelope, raw)?;
114 self.expected = self.expected.next().map_err(LineFault::SeqExhausted)?;
115 self.prev = chain_hash(raw);
116 Ok(checked)
117 }
118
119 pub(super) fn judge(raw: &[u8]) -> Result<Judged, LineFault> {
124 let envelope = readable_envelope(raw)?;
125 let (seq, prev) = (envelope.seq, envelope.prev);
126 let checked = classify(envelope, raw)?;
127 Ok(Judged { seq, prev, checked })
128 }
129}
130
131pub(super) struct Judged {
134 pub(super) seq: Seq,
135 pub(super) prev: B3Hash,
136 pub(super) checked: CheckedLine,
137}
138
139pub fn read_line(raw: &[u8]) -> Result<CheckedLine, LineFault> {
144 readable_envelope(raw).and_then(|envelope| classify(envelope, raw))
145}
146
147pub(crate) fn claimed_seq(raw: &[u8]) -> Option<Seq> {
151 readable_envelope(raw).ok().map(|envelope| envelope.seq)
152}
153
154fn readable_envelope(raw: &[u8]) -> Result<Envelope<'_>, LineFault> {
155 let envelope: Envelope = serde_json::from_slice(raw)
156 .map_err(|e| LineFault::NotALine(format!("not a ledger line: {e}")))?;
157 match readable_log_v(envelope.v) {
158 LogVersion::Current | LogVersion::Older => Ok(envelope),
159 LogVersion::Ahead => Err(LineFault::VersionAhead(envelope.v)),
160 LogVersion::NotAVersion => Err(LineFault::NotAVersion(envelope.v)),
161 }
162}
163
164fn classify(envelope: Envelope<'_>, raw: &[u8]) -> Result<CheckedLine, LineFault> {
167 let known = EventKind::deserialize(
168 IntoDeserializer::<serde::de::value::Error>::into_deserializer(envelope.kind.as_ref()),
169 );
170 match known {
171 Ok(_) => canonical_record(raw).map(CheckedLine::Known),
172 Err(_) if envelope.ig => Ok(CheckedLine::IgnoredUnknown(envelope.seq)),
173 Err(_) => Err(LineFault::UnknownKind(envelope.kind.into_owned())),
174 }
175}
176
177fn canonical_record(raw: &[u8]) -> Result<EventRecord, LineFault> {
178 let record = EventRecord::parse_line(raw)
179 .map_err(|e| LineFault::NotCanonical(format!("typed parse failed: {e}")))?;
180 let echo = record
181 .canonical_line()
182 .map_err(|e| LineFault::NotCanonical(format!("no canonical form: {e}")))?;
183 if echo != raw {
184 return Err(LineFault::NotCanonical(
185 "bytes are not writer-canonical".to_string(),
186 ));
187 }
188 Ok(record)
189}
190
191impl LineFault {
192 pub fn into_ax(self, line_no: u64) -> AxError {
197 let newer = |detail: String| {
198 AxError::failure(AxCode::LogVersionUnsupported, "verify ledger", detail).with_recovery(
199 "written by a newer sprawling; replay it with the version that wrote it",
200 )
201 };
202 let corrupt = |violation: String| {
203 AxError::failure(
204 AxCode::CasCorrupt,
205 "verify ledger",
206 format!("line {line_no}"),
207 )
208 .with_recovery(violation)
209 };
210 match self {
211 Self::VersionAhead(v) => newer(format!("line {line_no} carries v{v}")),
212 Self::UnknownKind(kind) => AxError::failure(
213 AxCode::LogVersionUnsupported,
214 "verify ledger",
215 format!("line {line_no} kind `{kind}`"),
216 )
217 .with_recovery(
218 "unknown kind without ig:true means a newer writer; use the sprawling that wrote it",
219 ),
220 Self::NotAVersion(v) => corrupt(format!("impossible v{v}")),
221 Self::NotALine(violation) | Self::NotCanonical(violation) => corrupt(violation),
222 Self::ChainBreak => corrupt("prev does not hash the previous line".to_string()),
223 Self::SeqGap { found, expected } => corrupt(format!(
224 "seq {} where {} was expected",
225 found.value(),
226 expected.value()
227 )),
228 Self::SeqExhausted(source) => source,
229 }
230 }
231}