use crate::error::{Result, StarError}; use cid::Cid; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; use sha2::{Digest, Sha256}; // --- STAR Types (Wire Format) --- /// The STAR Commit object #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StarCommit { pub did: String, pub version: i64, #[serde(default, skip_serializing_if = "Option::is_none")] pub data: Option, pub rev: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub prev: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub sig: Option, } /// The STAR MST Node object #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StarMstNode { #[serde(default, skip_serializing_if = "Option::is_none")] pub l: Option, #[serde(rename = "L", default, skip_serializing_if = "Option::is_none")] pub l_archived: Option, pub e: Vec, } /// The STAR MST Entry object #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StarMstEntry { pub p: u32, pub k: ByteBuf, #[serde(default, skip_serializing_if = "Option::is_none")] pub v: Option, #[serde(rename = "V", default, skip_serializing_if = "Option::is_none")] pub v_archived: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub t: Option, #[serde(rename = "T", default, skip_serializing_if = "Option::is_none")] pub t_archived: Option, } // --- CAR Repo Types (Canonical / Hashing Format) --- /// The Canonical MST Node object (for CID calculation) #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RepoMstNode { pub l: Option, pub e: Vec, } /// The Canonical MST Entry object #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RepoMstEntry { pub p: u32, pub k: ByteBuf, pub v: Cid, // Required in Repo spec pub t: Option, } // --- Conversion --- impl StarMstNode { pub fn to_repo(&self) -> Result { let mut entries = Vec::with_capacity(self.e.len()); for e in &self.e { entries.push(RepoMstEntry { p: e.p, k: e.k.clone(), v: e.v.ok_or_else(|| { StarError::InvalidState( "Cannot convert implicit STAR entry to Repo entry: missing 'v'".into(), ) })?, t: e.t, }); } Ok(RepoMstNode { l: self.l, e: entries, }) } } /// Calculates the MST height of a key (number of leading zero bits in SHA256 / 2). pub fn calculate_height(key: &[u8]) -> u32 { let digest = Sha256::digest(key); let mut zeros = 0; for &byte in digest.iter() { if byte == 0 { zeros += 8; } else { zeros += byte.leading_zeros(); break; } } zeros / 2 } /// A parsed item from the STAR stream #[derive(Debug, Clone)] pub enum StarItem { Commit(StarCommit), Node(StarMstNode), Record { key: Vec, cid: Cid, content: Option>, }, }