Streaming Tree ARchive format
1use crate::error::{Result, StarError};
2use cid::Cid;
3use serde::{Deserialize, Serialize};
4use serde_bytes::ByteBuf;
5use sha2::{Digest, Sha256};
6
7// --- STAR Types (Wire Format) ---
8
9/// The STAR Commit object
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct StarCommit {
12 pub did: String,
13 pub version: i64,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub data: Option<Cid>,
16 pub rev: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub prev: Option<Cid>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub sig: Option<ByteBuf>,
21}
22
23/// The STAR MST Node object
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct StarMstNode {
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub l: Option<Cid>,
28 #[serde(rename = "L", default, skip_serializing_if = "Option::is_none")]
29 pub l_archived: Option<bool>,
30 pub e: Vec<StarMstEntry>,
31}
32
33/// The STAR MST Entry object
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct StarMstEntry {
36 pub p: u32,
37 pub k: ByteBuf,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub v: Option<Cid>,
40 #[serde(rename = "V", default, skip_serializing_if = "Option::is_none")]
41 pub v_archived: Option<bool>,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub t: Option<Cid>,
44 #[serde(rename = "T", default, skip_serializing_if = "Option::is_none")]
45 pub t_archived: Option<bool>,
46}
47
48// --- CAR Repo Types (Canonical / Hashing Format) ---
49
50/// The Canonical MST Node object (for CID calculation)
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct RepoMstNode {
53 pub l: Option<Cid>,
54 pub e: Vec<RepoMstEntry>,
55}
56
57/// The Canonical MST Entry object
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct RepoMstEntry {
60 pub p: u32,
61 pub k: ByteBuf,
62 pub v: Cid, // Required in Repo spec
63 pub t: Option<Cid>,
64}
65
66// --- Conversion ---
67
68impl StarMstNode {
69 pub fn to_repo(&self) -> Result<RepoMstNode> {
70 let mut entries = Vec::with_capacity(self.e.len());
71 for e in &self.e {
72 entries.push(RepoMstEntry {
73 p: e.p,
74 k: e.k.clone(),
75 v: e.v.ok_or_else(|| {
76 StarError::InvalidState(
77 "Cannot convert implicit STAR entry to Repo entry: missing 'v'".into(),
78 )
79 })?,
80 t: e.t,
81 });
82 }
83 Ok(RepoMstNode {
84 l: self.l,
85 e: entries,
86 })
87 }
88}
89
90/// Calculates the MST height of a key (number of leading zero bits in SHA256 / 2).
91pub fn calculate_height(key: &[u8]) -> u32 {
92 let digest = Sha256::digest(key);
93 let mut zeros = 0;
94 for &byte in digest.iter() {
95 if byte == 0 {
96 zeros += 8;
97 } else {
98 zeros += byte.leading_zeros();
99 break;
100 }
101 }
102 zeros / 2
103}
104
105/// A parsed item from the STAR stream
106#[derive(Debug, Clone)]
107pub enum StarItem {
108 Commit(StarCommit),
109 Node(StarMstNode),
110 Record {
111 key: Vec<u8>,
112 cid: Cid,
113 content: Option<Vec<u8>>,
114 },
115}