tangled
alpha
login
or
join now
microcosm.blue
/
star
5
fork
atom
Streaming Tree ARchive format
5
fork
atom
overview
issues
pulls
pipelines
clippy
bad-example.com
1 month ago
555c5a2a
6326dfb3
+91
-72
5 changed files
expand all
collapse all
unified
split
src
error.rs
parser.rs
ser.rs
tests.rs
validation.rs
+22
-2
src/error.rs
···
1
1
+
use cid::Cid;
1
2
use std::io;
2
3
use thiserror::Error;
4
4
+
5
5
+
#[derive(Debug)]
6
6
+
pub enum VerificationKind {
7
7
+
Node,
8
8
+
Record { key: Vec<u8> },
9
9
+
}
3
10
4
11
#[derive(Error, Debug)]
5
12
pub enum StarError {
···
15
22
InvalidHeader,
16
23
#[error("Unexpected EOF")]
17
24
UnexpectedEof,
18
18
-
#[error("Verification failed: expected {expected}, got {computed}")]
19
19
-
VerificationFailed { expected: String, computed: String },
25
25
+
26
26
+
#[error("Verification failed for {kind:?}: expected {expected}, got {computed}")]
27
27
+
VerificationFailed {
28
28
+
kind: VerificationKind,
29
29
+
expected: Box<Cid>,
30
30
+
computed: Box<Cid>,
31
31
+
},
32
32
+
20
33
#[error("Invalid state: {0}")]
21
34
InvalidState(String),
35
35
+
36
36
+
#[error("Invalid structure: {0}")]
37
37
+
InvalidStructure(String),
38
38
+
39
39
+
#[error("Height mismatch: found {found}, expected {expected}")]
40
40
+
HeightMismatch { found: u32, expected: u32 },
41
41
+
22
42
#[error("Trailing data")]
23
43
TrailingData,
24
44
}
+43
-43
src/parser.rs
···
1
1
-
use crate::error::{Result, StarError};
1
1
+
use crate::error::{Result, StarError, VerificationKind};
2
2
use crate::types::{StarCommit, StarItem, StarMstNode};
3
3
use crate::validation::validate_node_structure;
4
4
use cid::Cid;
5
5
use sha2::{Digest, Sha256};
6
6
7
7
-
#[derive(Debug)]
7
7
+
#[derive(Debug, Default)]
8
8
enum State {
9
9
+
#[default]
9
10
Header,
10
11
Body {
11
12
stack: Vec<StackItem>,
···
29
30
node: StarMstNode,
30
31
parent_expected: Option<Cid>,
31
32
pending_records: Vec<(usize, Cid)>,
32
32
-
height: u32,
33
33
},
34
34
}
35
35
36
36
+
#[derive(Default)]
36
37
pub struct StarParser {
37
38
state: State,
38
39
}
···
114
115
}
115
116
116
117
fn parse_header(&mut self, buf: &[u8]) -> Result<Option<(usize, StarItem)>> {
117
117
-
if buf.len() < 1 {
118
118
+
if buf.is_empty() {
118
119
return Ok(None);
119
120
}
120
121
if buf[0] != 0x2A {
···
165
166
}
166
167
167
168
fn process_verification(stack: &mut Vec<StackItem>) -> Result<bool> {
168
168
-
if let Some(StackItem::VerifyLayer0 { .. }) = stack.last() {
169
169
-
if let Some(StackItem::VerifyLayer0 {
169
169
+
if let Some(StackItem::VerifyLayer0 { .. }) = stack.last()
170
170
+
&& let Some(StackItem::VerifyLayer0 {
170
171
mut node,
171
172
parent_expected,
172
173
pending_records,
173
174
..
174
175
}) = stack.pop()
175
175
-
{
176
176
-
for (idx, cid) in pending_records {
177
177
-
if idx < node.e.len() {
178
178
-
node.e[idx].v = Some(cid);
179
179
-
}
176
176
+
{
177
177
+
for (idx, cid) in pending_records {
178
178
+
if idx < node.e.len() {
179
179
+
node.e[idx].v = Some(cid);
180
180
}
181
181
+
}
181
182
182
182
-
let repo_node = node.to_repo()?;
183
183
-
let bytes = serde_ipld_dagcbor::to_vec(&repo_node)
184
184
-
.map_err(|e| StarError::Cbor(e.to_string()))?;
183
183
+
let repo_node = node.to_repo()?;
184
184
+
let bytes = serde_ipld_dagcbor::to_vec(&repo_node)
185
185
+
.map_err(|e| StarError::Cbor(e.to_string()))?;
185
186
186
186
-
let hash = Sha256::digest(&bytes);
187
187
-
let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?);
187
187
+
let hash = Sha256::digest(&bytes);
188
188
+
let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?);
188
189
189
189
-
if let Some(expected) = parent_expected {
190
190
-
if cid != expected {
191
191
-
return Err(StarError::VerificationFailed {
192
192
-
expected: expected.to_string(),
193
193
-
computed: cid.to_string(),
194
194
-
});
195
195
-
}
196
196
-
}
197
197
-
return Ok(true);
190
190
+
if let Some(expected) = parent_expected
191
191
+
&& cid != expected
192
192
+
{
193
193
+
return Err(StarError::VerificationFailed {
194
194
+
kind: VerificationKind::Node,
195
195
+
expected: Box::new(expected),
196
196
+
computed: Box::new(cid),
197
197
+
});
198
198
}
199
199
+
return Ok(true);
199
200
}
200
201
Ok(false)
201
202
}
···
230
231
231
232
// Check for implicit records (needed for VerifyLayer0 logic)
232
233
let mut has_implicit = false;
233
233
-
// Optimization: validate_node_structure already ensures consistency of v based on height.
234
234
-
// If height == 0, v is None (Implicit). If height > 0, v is Some (Explicit).
235
234
if height == 0 {
236
235
for e in &node.e {
237
236
if e.v_archived == Some(true) {
···
248
247
249
248
let hash = Sha256::digest(&bytes);
250
249
let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?);
251
251
-
if let Some(exp) = expected {
252
252
-
if cid != exp {
253
253
-
return Err(StarError::VerificationFailed {
254
254
-
expected: exp.to_string(),
255
255
-
computed: cid.to_string(),
256
256
-
});
257
257
-
}
250
250
+
if let Some(exp) = expected
251
251
+
&& cid != exp
252
252
+
{
253
253
+
return Err(StarError::VerificationFailed {
254
254
+
kind: VerificationKind::Node,
255
255
+
expected: Box::new(exp),
256
256
+
computed: Box::new(cid),
257
257
+
});
258
258
}
259
259
} else {
260
260
stack.push(StackItem::VerifyLayer0 {
261
261
node: node.clone(),
262
262
parent_expected: expected,
263
263
pending_records: Vec::new(),
264
264
-
height,
265
264
});
266
265
}
267
266
···
323
322
key: Vec<u8>,
324
323
expected: Option<Cid>,
325
324
implicit_index: Option<usize>,
326
326
-
stack: &mut Vec<StackItem>,
325
325
+
stack: &mut [StackItem],
327
326
) -> Result<Option<StarItem>> {
328
327
let hash = Sha256::digest(block_bytes);
329
328
let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?);
330
329
331
331
-
if let Some(exp) = expected {
332
332
-
if cid != exp {
333
333
-
return Err(StarError::VerificationFailed {
334
334
-
expected: exp.to_string(),
335
335
-
computed: cid.to_string(),
336
336
-
});
337
337
-
}
330
330
+
if let Some(exp) = expected
331
331
+
&& cid != exp
332
332
+
{
333
333
+
return Err(StarError::VerificationFailed {
334
334
+
kind: VerificationKind::Record { key: key.clone() },
335
335
+
expected: Box::new(exp),
336
336
+
computed: Box::new(cid),
337
337
+
});
338
338
}
339
339
340
340
if let Some(idx) = implicit_index {
+6
-5
src/ser.rs
···
111
111
112
112
// Validator State Machine (Moved from validation.rs)
113
113
114
114
-
#[derive(Debug)]
114
114
+
#[derive(Debug, Default)]
115
115
pub struct StarValidator {
116
116
state: ValidatorState,
117
117
}
118
118
119
119
-
#[derive(Debug)]
119
119
+
#[derive(Debug, Default)]
120
120
enum ValidatorState {
121
121
+
#[default]
121
122
Header,
122
122
-
Body { stack: Vec<Expectation> },
123
123
-
Done,
123
123
+
Body {
124
124
+
stack: Vec<Expectation>,
125
125
+
},
124
126
}
125
127
126
128
#[derive(Debug)]
···
220
222
pub fn is_done(&self) -> bool {
221
223
match &self.state {
222
224
ValidatorState::Body { stack } => stack.is_empty(),
223
223
-
ValidatorState::Done => true,
224
225
_ => false,
225
226
}
226
227
}
+6
-10
src/tests.rs
···
17
17
#[test]
18
18
fn test_roundtrip_basic() {
19
19
// 1. Create a dummy record
20
20
+
// Use "bar" (sha256 starts with fc... -> 0 leading zeros -> height 0)
20
21
let record_data = b"hello world";
21
22
let record_cid = create_test_cid(record_data);
22
22
-
let key = b"bar".to_vec(); // Height 0
23
23
+
let key = b"bar".to_vec();
23
24
24
25
// 2. Create a dummy MST Node (Layer 0, implicit record)
25
26
let node_entry = StarMstEntry {
···
191
192
let mut buf = Vec::new();
192
193
let mut serializer = StarSerializer::new(&mut buf);
193
194
194
194
-
// 1. Create invalid node (height 0 but empty)
195
195
+
// 1. Create invalid node (root empty)
195
196
let invalid_node = StarMstNode {
196
197
l: None,
197
198
l_archived: None,
···
215
216
// Node Fail
216
217
let err = serializer.write_node(&invalid_node).unwrap_err();
217
218
match err {
218
218
-
crate::error::StarError::InvalidState(msg) => {
219
219
-
assert!(
220
220
-
msg.contains("Root cannot be empty")
221
221
-
|| msg.contains("Height 0 cannot be empty"),
222
222
-
"Got message: {}",
223
223
-
msg
224
224
-
);
219
219
+
crate::error::StarError::InvalidStructure(msg) => {
220
220
+
assert!(msg.contains("Root cannot be empty"), "Got message: {}", msg);
225
221
}
226
226
-
e => panic!("Expected InvalidState, got {:?}", e),
222
222
+
e => panic!("Expected InvalidStructure, got {:?}", e),
227
223
}
228
224
}
229
225
}
+14
-12
src/validation.rs
···
30
30
31
31
if let Some(existing) = node_height {
32
32
if h != existing {
33
33
-
return Err(StarError::InvalidState(format!(
33
33
+
return Err(StarError::InvalidStructure(format!(
34
34
"Inconsistent key height in node: {} vs {}",
35
35
h, existing
36
36
)));
···
46
46
let height = match (node_height, expected) {
47
47
(Some(h), Some(exp)) => {
48
48
if h != exp {
49
49
-
return Err(StarError::InvalidState(format!(
50
50
-
"Height mismatch: found {}, expected {}",
51
51
-
h, exp
52
52
-
)));
49
49
+
return Err(StarError::HeightMismatch {
50
50
+
found: h,
51
51
+
expected: exp,
52
52
+
});
53
53
}
54
54
h
55
55
}
56
56
(Some(h), None) => h,
57
57
(None, Some(exp)) => {
58
58
if exp == 0 {
59
59
-
return Err(StarError::InvalidState("Height 0 cannot be empty".into()));
59
59
+
return Err(StarError::InvalidStructure(
60
60
+
"Height 0 cannot be empty".into(),
61
61
+
));
60
62
}
61
63
exp
62
64
}
63
63
-
(None, None) => return Err(StarError::InvalidState("Root cannot be empty".into())),
65
65
+
(None, None) => return Err(StarError::InvalidStructure("Root cannot be empty".into())),
64
66
};
65
67
66
68
if height == 0 {
67
69
if node.l.is_some() || node.l_archived.is_some() {
68
68
-
return Err(StarError::InvalidState(
70
70
+
return Err(StarError::InvalidStructure(
69
71
"Height 0 node cannot have left child".into(),
70
72
));
71
73
}
72
74
for e in &node.e {
73
75
if e.t.is_some() || e.t_archived.is_some() {
74
74
-
return Err(StarError::InvalidState(
76
76
+
return Err(StarError::InvalidStructure(
75
77
"Height 0 entries cannot have subtrees".into(),
76
78
));
77
79
}
78
80
if e.v.is_some() {
79
79
-
return Err(StarError::InvalidState(
81
81
+
return Err(StarError::InvalidStructure(
80
82
"Height 0 node must omit record CIDs".into(),
81
83
));
82
84
}
83
85
}
84
86
} else {
85
87
if node.e.is_empty() && node.l.is_none() {
86
86
-
return Err(StarError::InvalidState(
88
88
+
return Err(StarError::InvalidStructure(
87
89
"Empty intermediate node must have left child".into(),
88
90
));
89
91
}
90
92
for e in &node.e {
91
93
if e.v.is_none() {
92
92
-
return Err(StarError::InvalidState(
94
94
+
return Err(StarError::InvalidStructure(
93
95
"Intermediate node must include record CIDs".into(),
94
96
));
95
97
}