STreaming ARchives: stricter, verifiable, deterministic, highly compressible alternatives to CAR files for atproto repositories.
atproto car
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor where verification things live

phil 5ecc07f8 83085039

+278 -341
+1 -2
src/lib.rs
··· 12 12 13 13 pub use error::{Result, StarError}; 14 14 pub use parser::StarParser; 15 - pub use ser::{StarEncoder, StarSerializer}; 15 + pub use ser::{StarEncoder, StarSerializer, StarValidator}; 16 16 pub use types::{RepoMstEntry, RepoMstNode, StarCommit, StarItem, StarMstEntry, StarMstNode}; 17 - pub use validation::StarValidator; 18 17 19 18 #[cfg(feature = "blocking")] 20 19 pub use blocking::StarIterator;
+42 -152
src/parser.rs
··· 1 1 use crate::error::{Result, StarError}; 2 2 use crate::types::{StarCommit, StarItem, StarMstNode}; 3 + use crate::validation::validate_node_structure; 3 4 use cid::Cid; 4 5 use sha2::{Digest, Sha256}; 5 6 6 7 #[derive(Debug)] 7 8 enum State { 8 9 Header, 9 - Body { 10 + Body { 10 11 stack: Vec<StackItem>, 11 12 current_len: Option<usize>, 12 13 }, ··· 17 18 enum StackItem { 18 19 Node { 19 20 expected: Option<Cid>, 20 - expected_height: Option<u32>, // The node at this position must have height exactly == this value (if set) 21 + expected_height: Option<u32>, 21 22 }, 22 23 Record { 23 24 key: Vec<u8>, ··· 45 46 46 47 pub fn parse(&mut self, buf: &[u8]) -> Result<(usize, Option<StarItem>)> { 47 48 let mut consumed = 0; 48 - 49 + 49 50 loop { 50 51 let is_body_done = if let State::Body { stack, .. } = &self.state { 51 52 stack.is_empty() ··· 79 80 Some((n, len)) => (n, len), 80 81 None => return Ok((consumed, None)), 81 82 }; 82 - 83 + 83 84 let body_buf = &current_buf[len_consumed..]; 84 85 if body_buf.len() < len { 85 86 consumed += len_consumed; ··· 88 89 89 90 let block_bytes = &body_buf[..len]; 90 91 *current_len = None; 91 - 92 + 92 93 let item = stack.pop().unwrap(); 93 94 let result_item = match item { 94 - StackItem::Node { 95 - expected, 96 - expected_height, 97 - } => Self::process_node(block_bytes, expected, expected_height, stack)?, 98 - StackItem::Record { 99 - key, 100 - expected, 101 - implicit_index, 102 - } => { 95 + StackItem::Node { expected, expected_height } => { 96 + Self::process_node(block_bytes, expected, expected_height, stack)? 97 + }, 98 + StackItem::Record { key, expected, implicit_index } => { 103 99 Self::process_record(block_bytes, key, expected, implicit_index, stack)? 104 - } 100 + }, 105 101 _ => return Err(StarError::InvalidState("Unexpected stack item".into())), 106 102 }; 107 103 ··· 112 108 } 113 109 } 114 110 115 - fn calculate_height(key: &[u8]) -> u32 { 116 - let digest = Sha256::digest(key); 117 - let mut zeros = 0; 118 - for &byte in digest.iter() { 119 - if byte == 0 { 120 - zeros += 8; 121 - } else { 122 - zeros += byte.leading_zeros(); 123 - break; 124 - } 125 - } 126 - zeros / 2 127 - } 128 - 129 111 fn parse_header(&mut self, buf: &[u8]) -> Result<Option<(usize, StarItem)>> { 130 112 if buf.len() < 1 { 131 113 return Ok(None); ··· 135 117 } 136 118 137 119 let slice = &buf[1..]; 138 - 120 + 139 121 let (ver, remaining1) = match unsigned_varint::decode::usize(slice) { 140 122 Ok(res) => res, 141 123 Err(unsigned_varint::decode::Error::Insufficient) => return Ok(None), 142 124 Err(e) => return Err(StarError::InvalidState(format!("Varint error: {}", e))), 143 125 }; 144 - 126 + 145 127 let (len, remaining2) = match unsigned_varint::decode::usize(remaining1) { 146 128 Ok(res) => res, 147 129 Err(unsigned_varint::decode::Error::Insufficient) => return Ok(None), 148 130 Err(e) => return Err(StarError::InvalidState(format!("Varint error: {}", e))), 149 131 }; 150 132 151 - let header_varints_len = buf.len() - 1 - remaining2.len(); 152 - let total_header_len = 1 + header_varints_len; 133 + let header_varints_len = buf.len() - 1 - remaining2.len(); 134 + let total_header_len = 1 + header_varints_len; 153 135 let total_len = total_header_len + len; 154 136 155 137 if buf.len() < total_len { ··· 159 141 let commit_bytes = &buf[total_header_len..total_len]; 160 142 let commit: StarCommit = serde_ipld_dagcbor::from_slice(commit_bytes) 161 143 .map_err(|e| StarError::Cbor(e.to_string()))?; 162 - 163 - let _ = ver; 144 + 145 + let _ = ver; 164 146 165 147 let mut stack = Vec::new(); 166 148 if let Some(root_cid) = commit.data { ··· 170 152 }); 171 153 } 172 154 173 - self.state = State::Body { 155 + self.state = State::Body { 174 156 stack, 175 - current_len: None, 157 + current_len: None 176 158 }; 177 159 Ok(Some((total_len, StarItem::Commit(commit)))) 178 160 } ··· 223 205 let consumed = buf.len() - remaining.len(); 224 206 *current_len = Some(l); 225 207 Ok(Some((consumed, l))) 226 - } 208 + }, 227 209 Err(unsigned_varint::decode::Error::Insufficient) => Ok(None), 228 210 Err(e) => Err(StarError::InvalidState(format!("Varint error: {}", e))), 229 211 } 230 212 } 231 213 232 - fn process_node( 233 - block_bytes: &[u8], 234 - expected: Option<Cid>, 235 - expected_height: Option<u32>, 236 - stack: &mut Vec<StackItem>, 237 - ) -> Result<Option<StarItem>> { 214 + fn process_node(block_bytes: &[u8], expected: Option<Cid>, expected_height: Option<u32>, stack: &mut Vec<StackItem>) -> Result<Option<StarItem>> { 238 215 let node: StarMstNode = serde_ipld_dagcbor::from_slice(block_bytes) 239 216 .map_err(|e| StarError::Cbor(e.to_string()))?; 240 217 241 - // We run the loop to reconstruct keys and validate their internal consistency first. 242 - let mut prev_key_bytes = Vec::new(); 243 - let mut entry_keys = Vec::new(); 244 - let mut node_height = None; 245 - 246 - for e in &node.e { 247 - let mut key = if e.p as usize <= prev_key_bytes.len() { 248 - prev_key_bytes[..e.p as usize].to_vec() 249 - } else { 250 - prev_key_bytes.clone() 251 - }; 252 - key.extend_from_slice(&e.k); 253 - 254 - let h = Self::calculate_height(&key); 255 - 256 - if let Some(existing_h) = node_height { 257 - if h != existing_h { 258 - return Err(StarError::InvalidState(format!( 259 - "Inconsistent key height in node: {} vs {}", 260 - h, existing_h 261 - ))); 262 - } 263 - } else { 264 - node_height = Some(h); 265 - } 266 - 267 - entry_keys.push(key.clone()); 268 - prev_key_bytes = key; 269 - } 270 - 271 - // 2. Validate Height against Expectation 272 - let height = match (node_height, expected_height) { 273 - (Some(h), Some(exp)) => { 274 - if h != exp { 275 - return Err(StarError::InvalidState(format!( 276 - "Invalid MST height: found {}, expected {}", 277 - h, exp 278 - ))); 279 - } 280 - h 281 - } 282 - (Some(h), None) => { 283 - // Root node with keys. 284 - h 285 - } 286 - (None, Some(exp)) => { 287 - // Empty node (intermediate). Must match expectation. 288 - // Constraint: Height 0 cannot be empty. 289 - if exp == 0 { 290 - return Err(StarError::InvalidState( 291 - "MST nodes at depth=0 cannot be empty".into(), 292 - )); 293 - } 294 - exp 295 - } 296 - (None, None) => { 297 - // Root node empty. 298 - return Err(StarError::InvalidState( 299 - "Root node must contain entries".into(), 300 - )); 301 - } 302 - }; 218 + // Use shared validation logic 219 + let (height, entry_keys) = validate_node_structure(&node, expected_height)?; 303 220 304 - // 3. Validate Structure based on Height 221 + // Check for implicit records (needed for VerifyLayer0 logic) 222 + let mut has_implicit = false; 223 + // Optimization: validate_node_structure already ensures consistency of v based on height. 224 + // If height == 0, v is None (Implicit). If height > 0, v is Some (Explicit). 305 225 if height == 0 { 306 - // Must have no children 307 - if node.l.is_some() || node.l_archived.is_some() { 308 - return Err(StarError::InvalidState( 309 - "Height 0 node cannot have left child".into(), 310 - )); 311 - } 312 226 for e in &node.e { 313 - if e.t.is_some() || e.t_archived.is_some() { 314 - return Err(StarError::InvalidState( 315 - "Height 0 entries cannot have subtrees".into(), 316 - )); 227 + if e.v_archived == Some(true) { 228 + has_implicit = true; 229 + break; 317 230 } 318 231 } 319 - } else { 320 - // Height > 0 321 - if node.e.is_empty() && node.l.is_none() { 322 - return Err(StarError::InvalidState( 323 - "Empty intermediate node must have left child".into(), 324 - )); 325 - } 326 - } 327 - 328 - // Check for implicit records 329 - let mut has_implicit = false; 330 - for e in &node.e { 331 - if e.v_archived == Some(true) && e.v.is_none() { 332 - has_implicit = true; 333 - break; 334 - } 335 232 } 336 233 337 234 if !has_implicit { ··· 340 237 .map_err(|e| StarError::Cbor(e.to_string()))?; 341 238 342 239 let hash = Sha256::digest(&bytes); 343 - let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 240 + let cid = Cid::new_v1( 241 + 0x71, 242 + cid::multihash::Multihash::wrap(0x12, &hash)?, 243 + ); 344 244 if let Some(exp) = expected { 345 245 if cid != exp { 346 246 return Err(StarError::VerificationFailed { ··· 359 259 } 360 260 361 261 // Push children in reverse 362 - // Expected height for children is strictly height - 1 363 262 let child_expected_height = height.checked_sub(1); 364 - 365 - // If height is 0, we already verified no children exist, so child_expected_height is None/invalid but unused. 366 - // Actually height - 1 would underflow if height 0. But logic ensures no children pushed if height 0. 367 - 263 + 368 264 if height > 0 { 369 265 let next_h = child_expected_height.unwrap(); 370 - 266 + 371 267 for i in (0..node.e.len()).rev() { 372 268 let e = &node.e[i]; 373 269 let key = entry_keys[i].clone(); 374 270 375 271 if e.t_archived == Some(true) { 376 - stack.push(StackItem::Node { 272 + stack.push(StackItem::Node { 377 273 expected: e.t, 378 - expected_height: Some(next_h), 274 + expected_height: Some(next_h), 379 275 }); 380 276 } 381 277 ··· 390 286 } 391 287 392 288 if node.l_archived == Some(true) { 393 - stack.push(StackItem::Node { 289 + stack.push(StackItem::Node { 394 290 expected: node.l, 395 291 expected_height: Some(next_h), 396 292 }); 397 293 } 398 294 } else { 399 295 // Height 0: Push records only 400 - for i in (0..node.e.len()).rev() { 296 + for i in (0..node.e.len()).rev() { 401 297 let e = &node.e[i]; 402 298 let key = entry_keys[i].clone(); 403 - 299 + 404 300 if e.v_archived == Some(true) { 405 301 let implicit_index = if e.v.is_none() { Some(i) } else { None }; 406 302 stack.push(StackItem::Record { ··· 415 311 Ok(Some(StarItem::Node(node))) 416 312 } 417 313 418 - fn process_record( 419 - block_bytes: &[u8], 420 - key: Vec<u8>, 421 - expected: Option<Cid>, 422 - implicit_index: Option<usize>, 423 - stack: &mut Vec<StackItem>, 424 - ) -> Result<Option<StarItem>> { 314 + fn process_record(block_bytes: &[u8], key: Vec<u8>, expected: Option<Cid>, implicit_index: Option<usize>, stack: &mut Vec<StackItem>) -> Result<Option<StarItem>> { 425 315 let hash = Sha256::digest(block_bytes); 426 316 let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 427 317
+118 -16
src/ser.rs
··· 1 1 use crate::error::Result; 2 2 use crate::types::{StarCommit, StarItem, StarMstNode}; 3 - use crate::validation::StarValidator; 3 + use crate::validation::validate_node_structure; 4 + use crate::error::StarError; 4 5 use std::io::Write; 5 6 6 7 pub struct StarEncoder; ··· 14 15 15 16 pub fn write_header<W: Write>(commit: &StarCommit, dst: &mut W) -> Result<()> { 16 17 dst.write_all(&[0x2A])?; 17 - 18 + 18 19 Self::write_varint(1, dst)?; 19 - 20 + 20 21 let commit_bytes = serde_ipld_dagcbor::to_vec(commit) 21 22 .map_err(|e| crate::error::StarError::Cbor(e.to_string()))?; 22 - 23 + 23 24 Self::write_varint(commit_bytes.len(), dst)?; 24 25 dst.write_all(&commit_bytes)?; 25 - 26 + 26 27 Ok(()) 27 28 } 28 29 29 30 pub fn write_node<W: Write>(node: &StarMstNode, dst: &mut W) -> Result<()> { 30 31 let node_bytes = serde_ipld_dagcbor::to_vec(node) 31 32 .map_err(|e| crate::error::StarError::Cbor(e.to_string()))?; 32 - 33 + 33 34 Self::write_varint(node_bytes.len(), dst)?; 34 35 dst.write_all(&node_bytes)?; 35 - 36 + 36 37 Ok(()) 37 38 } 38 39 39 40 pub fn write_record<W: Write>(record_bytes: &[u8], dst: &mut W) -> Result<()> { 40 41 Self::write_varint(record_bytes.len(), dst)?; 41 42 dst.write_all(record_bytes)?; 42 - 43 + 43 44 Ok(()) 44 45 } 45 46 } ··· 52 53 use bytes::BufMut; 53 54 // BytesMut::writer() returns an impl Write 54 55 let mut writer = dst.writer(); 55 - 56 + 56 57 match item { 57 58 StarItem::Commit(c) => Self::write_header(&c, &mut writer), 58 59 StarItem::Node(n) => Self::write_node(&n, &mut writer), ··· 60 61 if let Some(bytes) = content { 61 62 Self::write_record(&bytes, &mut writer) 62 63 } else { 63 - Err(crate::error::StarError::InvalidState( 64 - "Cannot serialize record without content".into(), 65 - )) 64 + Err(crate::error::StarError::InvalidState("Cannot serialize record without content".into())) 66 65 } 67 66 } 68 67 } ··· 97 96 self.validator.accept_record(record_bytes)?; 98 97 StarEncoder::write_record(record_bytes, &mut self.writer) 99 98 } 100 - 99 + 101 100 pub fn finish(self) -> Result<W> { 102 101 if !self.validator.is_done() { 103 - return Err(crate::error::StarError::InvalidState( 104 - "Incomplete tree".into(), 105 - )); 102 + return Err(crate::error::StarError::InvalidState("Incomplete tree".into())); 106 103 } 107 104 Ok(self.writer) 108 105 } 109 106 } 107 + 108 + // Validator State Machine (Moved from validation.rs) 109 + 110 + #[derive(Debug)] 111 + pub struct StarValidator { 112 + state: ValidatorState, 113 + } 114 + 115 + #[derive(Debug)] 116 + enum ValidatorState { 117 + Header, 118 + Body { stack: Vec<Expectation> }, 119 + Done, 120 + } 121 + 122 + #[derive(Debug)] 123 + enum Expectation { 124 + Root, 125 + Node { height: u32 }, 126 + Record, 127 + } 128 + 129 + impl StarValidator { 130 + pub fn new() -> Self { 131 + Self { state: ValidatorState::Header } 132 + } 133 + 134 + pub fn accept_header(&mut self, commit: &StarCommit) -> Result<()> { 135 + match &self.state { 136 + ValidatorState::Header => { 137 + let stack = if commit.data.is_some() { 138 + vec![Expectation::Root] 139 + } else { 140 + Vec::new() // Empty tree 141 + }; 142 + self.state = ValidatorState::Body { stack }; 143 + Ok(()) 144 + }, 145 + _ => Err(StarError::InvalidState("Header already written or invalid state".into())), 146 + } 147 + } 148 + 149 + pub fn accept_node(&mut self, node: &StarMstNode) -> Result<()> { 150 + match &mut self.state { 151 + ValidatorState::Body { stack } => { 152 + if stack.is_empty() { 153 + return Err(StarError::InvalidState("Unexpected node: tree is complete".into())); 154 + } 155 + 156 + let expectation = stack.pop().unwrap(); 157 + let expected_height = match expectation { 158 + Expectation::Record => { 159 + return Err(StarError::InvalidState("Expected record, got node".into())); 160 + }, 161 + Expectation::Root => None, 162 + Expectation::Node { height } => Some(height), 163 + }; 164 + 165 + // Use the shared validation logic 166 + let (height, _) = validate_node_structure(node, expected_height)?; 167 + 168 + let child_height = if height > 0 { height - 1 } else { 0 }; 169 + 170 + for e in node.e.iter().rev() { 171 + if e.t_archived == Some(true) { 172 + stack.push(Expectation::Node { height: child_height }); 173 + } 174 + if e.v_archived == Some(true) { 175 + stack.push(Expectation::Record); 176 + } 177 + } 178 + 179 + if node.l_archived == Some(true) { 180 + stack.push(Expectation::Node { height: child_height }); 181 + } 182 + 183 + Ok(()) 184 + }, 185 + _ => Err(StarError::InvalidState("Invalid state for node".into())), 186 + } 187 + } 188 + 189 + pub fn accept_record(&mut self, _bytes: &[u8]) -> Result<()> { 190 + match &mut self.state { 191 + ValidatorState::Body { stack } => { 192 + if stack.is_empty() { 193 + return Err(StarError::InvalidState("Unexpected record: tree is complete".into())); 194 + } 195 + match stack.pop().unwrap() { 196 + Expectation::Record => Ok(()), 197 + _ => Err(StarError::InvalidState("Expected node, got record".into())), 198 + } 199 + }, 200 + _ => Err(StarError::InvalidState("Invalid state for record".into())), 201 + } 202 + } 203 + 204 + pub fn is_done(&self) -> bool { 205 + match &self.state { 206 + ValidatorState::Body { stack } => stack.is_empty(), 207 + ValidatorState::Done => true, 208 + _ => false, 209 + } 210 + } 211 + }
+34 -16
src/tests.rs
··· 19 19 // 1. Create a dummy record 20 20 let record_data = b"hello world"; 21 21 let record_cid = create_test_cid(record_data); 22 - let key = b"foo".to_vec(); 22 + let key = b"bar".to_vec(); // Height 0 23 23 24 24 // 2. Create a dummy MST Node (Layer 0, implicit record) 25 25 let node_entry = StarMstEntry { ··· 71 71 let mut parser = StarParser::new(); 72 72 73 73 // Helper to mimic Decoder loop for tests 74 - fn parse_helper(parser: &mut StarParser, buf: &mut Vec<u8>, offset: &mut usize) -> Option<StarItem> { 74 + fn parse_helper( 75 + parser: &mut StarParser, 76 + buf: &mut Vec<u8>, 77 + offset: &mut usize, 78 + ) -> Option<StarItem> { 75 79 let (consumed, item) = parser.parse(&buf[*offset..]).unwrap(); 76 80 *offset += consumed; 77 81 item 78 82 } 79 - 83 + 80 84 let mut offset = 0; 81 85 82 86 // Header ··· 119 123 fn test_verification_failure() { 120 124 // 1. Create a dummy record 121 125 let record_data = b"hello world"; 122 - let key = b"foo".to_vec(); 126 + let key = b"bar".to_vec(); // Height 0 123 127 124 128 // 2. Create STAR Node 125 129 let node_entry = StarMstEntry { ··· 156 160 // 5. Parse 157 161 let mut parser = StarParser::new(); 158 162 let mut offset = 0; 159 - 160 - fn parse_helper(parser: &mut StarParser, buf: &mut Vec<u8>, offset: &mut usize) -> Result<Option<StarItem>, crate::error::StarError> { 163 + 164 + fn parse_helper( 165 + parser: &mut StarParser, 166 + buf: &mut Vec<u8>, 167 + offset: &mut usize, 168 + ) -> Result<Option<StarItem>, crate::error::StarError> { 161 169 let (consumed, item) = parser.parse(&buf[*offset..])?; 162 170 *offset += consumed; 163 171 Ok(item) ··· 166 174 parse_helper(&mut parser, &mut buf, &mut offset).unwrap(); // Header OK 167 175 parse_helper(&mut parser, &mut buf, &mut offset).unwrap(); // Node OK 168 176 parse_helper(&mut parser, &mut buf, &mut offset).unwrap(); // Record OK 169 - 177 + 170 178 // 6. Trigger verification 171 179 let result = parse_helper(&mut parser, &mut buf, &mut offset); 172 - 180 + 173 181 assert!(result.is_err()); 174 182 match result.unwrap_err() { 175 - crate::error::StarError::VerificationFailed { .. } => {}, 183 + crate::error::StarError::VerificationFailed { .. } => {} 176 184 e => panic!("Expected VerificationFailed, got {:?}", e), 177 185 } 178 186 } ··· 182 190 // Test that strict serializer enforces constraints 183 191 let mut buf = Vec::new(); 184 192 let mut serializer = StarSerializer::new(&mut buf); 185 - 193 + 186 194 // 1. Create invalid node (height 0 but empty) 187 195 let invalid_node = StarMstNode { 188 196 l: None, 189 197 l_archived: None, 190 198 e: vec![], 191 199 }; 192 - 200 + 193 201 // 2. Commit pointing to root 194 202 let cid = create_test_cid(b"foo"); 195 203 let commit = StarCommit { 196 - did: "did".into(), version: 3, data: Some(cid), rev: "1".into(), prev: None, sig: None 204 + did: "did".into(), 205 + version: 3, 206 + data: Some(cid), 207 + rev: "1".into(), 208 + prev: None, 209 + sig: None, 197 210 }; 198 - 211 + 199 212 // Header OK 200 213 serializer.write_header(&commit).unwrap(); 201 - 214 + 202 215 // Node Fail 203 216 let err = serializer.write_node(&invalid_node).unwrap_err(); 204 217 match err { 205 218 crate::error::StarError::InvalidState(msg) => { 206 - assert!(msg.contains("Root node must contain entries") || msg.contains("Height 0 cannot be empty")); 207 - }, 219 + assert!( 220 + msg.contains("Root cannot be empty") 221 + || msg.contains("Height 0 cannot be empty"), 222 + "Got message: {}", 223 + msg 224 + ); 225 + } 208 226 e => panic!("Expected InvalidState, got {:?}", e), 209 227 } 210 228 }
+83 -155
src/validation.rs
··· 1 1 use crate::error::{Result, StarError}; 2 - use crate::types::{calculate_height, StarCommit, StarMstNode}; 2 + use crate::types::{calculate_height, StarMstNode}; 3 3 4 - #[derive(Debug)] 5 - pub struct StarValidator { 6 - state: State, 7 - } 8 - 9 - #[derive(Debug)] 10 - enum State { 11 - Header, 12 - Body { stack: Vec<Expectation> }, 13 - Done, 14 - } 4 + /// Validates the structure of a STAR MST node. 5 + /// 6 + /// Checks: 7 + /// - Key height consistency (all keys must have same height) 8 + /// - Height matching against expectation 9 + /// - Structural rules (Height 0 vs >0 constraints) 10 + /// - Implicit/Explicit V rules 11 + /// 12 + /// Returns (height, reconstructed_keys) 13 + pub fn validate_node_structure( 14 + node: &StarMstNode, 15 + expected: Option<u32>, 16 + ) -> Result<(u32, Vec<Vec<u8>>)> { 17 + let mut node_height = None; 18 + let mut prev_key_bytes = Vec::new(); 19 + let mut entry_keys = Vec::new(); 15 20 16 - #[derive(Debug)] 17 - enum Expectation { 18 - Root, 19 - Node { height: u32 }, 20 - Record, 21 - } 21 + for e in &node.e { 22 + let mut key = if e.p as usize <= prev_key_bytes.len() { 23 + prev_key_bytes[..e.p as usize].to_vec() 24 + } else { 25 + prev_key_bytes.clone() 26 + }; 27 + key.extend_from_slice(&e.k); 22 28 23 - impl StarValidator { 24 - pub fn new() -> Self { 25 - Self { state: State::Header } 26 - } 29 + let h = calculate_height(&key); 27 30 28 - pub fn accept_header(&mut self, commit: &StarCommit) -> Result<()> { 29 - match &self.state { 30 - State::Header => { 31 - let stack = if commit.data.is_some() { 32 - vec![Expectation::Root] 33 - } else { 34 - Vec::new() // Empty tree 35 - }; 36 - self.state = State::Body { stack }; 37 - Ok(()) 38 - }, 39 - _ => Err(StarError::InvalidState("Header already written or invalid state".into())), 31 + if let Some(existing) = node_height { 32 + if h != existing { 33 + return Err(StarError::InvalidState(format!( 34 + "Inconsistent key height in node: {} vs {}", 35 + h, existing 36 + ))); 37 + } 38 + } else { 39 + node_height = Some(h); 40 40 } 41 - } 42 41 43 - pub fn accept_node(&mut self, node: &StarMstNode) -> Result<()> { 44 - match &mut self.state { 45 - State::Body { stack } => { 46 - if stack.is_empty() { 47 - return Err(StarError::InvalidState("Unexpected node: tree is complete".into())); 48 - } 49 - 50 - let expectation = stack.pop().unwrap(); 51 - let height = match expectation { 52 - Expectation::Record => { 53 - return Err(StarError::InvalidState("Expected record, got node".into())); 54 - }, 55 - Expectation::Root => { 56 - if node.e.is_empty() { 57 - return Err(StarError::InvalidState("Root node must contain entries".into())); 58 - } 59 - Self::validate_node_height(node, None)? 60 - }, 61 - Expectation::Node { height } => { 62 - Self::validate_node_height(node, Some(height))? 63 - } 64 - }; 65 - 66 - if height == 0 { 67 - if node.l.is_some() || node.l_archived.is_some() { 68 - return Err(StarError::InvalidState("Height 0 node cannot have left child".into())); 69 - } 70 - for e in &node.e { 71 - if e.t.is_some() || e.t_archived.is_some() { 72 - return Err(StarError::InvalidState("Height 0 entries cannot have subtrees".into())); 73 - } 74 - } 75 - } else { 76 - if node.e.is_empty() && node.l.is_none() { 77 - return Err(StarError::InvalidState("Empty intermediate node must have left child".into())); 78 - } 79 - } 80 - 81 - let child_height = if height > 0 { height - 1 } else { 0 }; 82 - 83 - for e in node.e.iter().rev() { 84 - if e.t_archived == Some(true) { 85 - stack.push(Expectation::Node { height: child_height }); 86 - } 87 - 88 - if e.v_archived == Some(true) { 89 - if height == 0 && e.v.is_some() { 90 - return Err(StarError::InvalidState("Height 0 node must omit record CIDs".into())); 91 - } 92 - if height > 0 && e.v.is_none() { 93 - return Err(StarError::InvalidState("Intermediate node must include record CIDs".into())); 94 - } 95 - stack.push(Expectation::Record); 96 - } 97 - } 98 - 99 - if node.l_archived == Some(true) { 100 - stack.push(Expectation::Node { height: child_height }); 101 - } 102 - 103 - Ok(()) 104 - }, 105 - _ => Err(StarError::InvalidState("Invalid state for node".into())), 106 - } 42 + entry_keys.push(key.clone()); 43 + prev_key_bytes = key; 107 44 } 108 45 109 - pub fn accept_record(&mut self, _bytes: &[u8]) -> Result<()> { 110 - match &mut self.state { 111 - State::Body { stack } => { 112 - if stack.is_empty() { 113 - return Err(StarError::InvalidState("Unexpected record: tree is complete".into())); 114 - } 115 - match stack.pop().unwrap() { 116 - Expectation::Record => Ok(()), 117 - _ => Err(StarError::InvalidState("Expected node, got record".into())), 118 - } 119 - }, 120 - _ => Err(StarError::InvalidState("Invalid state for record".into())), 46 + let height = match (node_height, expected) { 47 + (Some(h), Some(exp)) => { 48 + if h != exp { 49 + return Err(StarError::InvalidState(format!( 50 + "Height mismatch: found {}, expected {}", 51 + h, exp 52 + ))); 53 + } 54 + h 121 55 } 122 - } 56 + (Some(h), None) => h, 57 + (None, Some(exp)) => { 58 + if exp == 0 { 59 + return Err(StarError::InvalidState("Height 0 cannot be empty".into())); 60 + } 61 + exp 62 + } 63 + (None, None) => return Err(StarError::InvalidState("Root cannot be empty".into())), 64 + }; 123 65 124 - fn validate_node_height(node: &StarMstNode, expected: Option<u32>) -> Result<u32> { 125 - let mut node_height = None; 126 - let mut prev_key_bytes = Vec::new(); 127 - 66 + if height == 0 { 67 + if node.l.is_some() || node.l_archived.is_some() { 68 + return Err(StarError::InvalidState( 69 + "Height 0 node cannot have left child".into(), 70 + )); 71 + } 128 72 for e in &node.e { 129 - let mut key = if e.p as usize <= prev_key_bytes.len() { 130 - prev_key_bytes[..e.p as usize].to_vec() 131 - } else { 132 - prev_key_bytes.clone() 133 - }; 134 - key.extend_from_slice(&e.k); 135 - 136 - let h = calculate_height(&key); 137 - 138 - if let Some(existing) = node_height { 139 - if h != existing { 140 - return Err(StarError::InvalidState(format!("Inconsistent key height in node: {} vs {}", h, existing))); 141 - } 142 - } else { 143 - node_height = Some(h); 73 + if e.t.is_some() || e.t_archived.is_some() { 74 + return Err(StarError::InvalidState( 75 + "Height 0 entries cannot have subtrees".into(), 76 + )); 144 77 } 145 - prev_key_bytes = key; 78 + if e.v.is_some() { 79 + return Err(StarError::InvalidState( 80 + "Height 0 node must omit record CIDs".into(), 81 + )); 82 + } 83 + } 84 + } else { 85 + if node.e.is_empty() && node.l.is_none() { 86 + return Err(StarError::InvalidState( 87 + "Empty intermediate node must have left child".into(), 88 + )); 146 89 } 147 - 148 - let height = match (node_height, expected) { 149 - (Some(h), Some(exp)) => { 150 - if h != exp { 151 - return Err(StarError::InvalidState(format!("Height mismatch: found {}, expected {}", h, exp))); 152 - } 153 - h 154 - }, 155 - (Some(h), None) => h, 156 - (None, Some(exp)) => { 157 - if exp == 0 { return Err(StarError::InvalidState("Height 0 cannot be empty".into())); } 158 - exp 159 - }, 160 - (None, None) => return Err(StarError::InvalidState("Root cannot be empty".into())), 161 - }; 162 - Ok(height) 163 - } 164 - 165 - pub fn is_done(&self) -> bool { 166 - match &self.state { 167 - State::Body { stack } => stack.is_empty(), 168 - State::Done => true, // Not reachable with current logic but semantically true 169 - _ => false, 90 + for e in &node.e { 91 + if e.v.is_none() { 92 + return Err(StarError::InvalidState( 93 + "Intermediate node must include record CIDs".into(), 94 + )); 95 + } 170 96 } 171 97 } 98 + 99 + Ok((height, entry_keys)) 172 100 }