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.

draft

phil 1ac5f1de 3c6ebc8a

+1080
+1
.gitignore
··· 1 + /target
+507
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "base-x" 7 + version = "0.2.11" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 10 + 11 + [[package]] 12 + name = "base256emoji" 13 + version = "1.0.2" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 16 + dependencies = [ 17 + "const-str", 18 + "match-lookup", 19 + ] 20 + 21 + [[package]] 22 + name = "block-buffer" 23 + version = "0.10.4" 24 + source = "registry+https://github.com/rust-lang/crates.io-index" 25 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 26 + dependencies = [ 27 + "generic-array", 28 + ] 29 + 30 + [[package]] 31 + name = "bytes" 32 + version = "1.11.0" 33 + source = "registry+https://github.com/rust-lang/crates.io-index" 34 + checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 35 + 36 + [[package]] 37 + name = "cbor4ii" 38 + version = "0.2.14" 39 + source = "registry+https://github.com/rust-lang/crates.io-index" 40 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 41 + dependencies = [ 42 + "serde", 43 + ] 44 + 45 + [[package]] 46 + name = "cfg-if" 47 + version = "1.0.4" 48 + source = "registry+https://github.com/rust-lang/crates.io-index" 49 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 50 + 51 + [[package]] 52 + name = "cid" 53 + version = "0.11.1" 54 + source = "registry+https://github.com/rust-lang/crates.io-index" 55 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 56 + dependencies = [ 57 + "core2", 58 + "multibase", 59 + "multihash", 60 + "serde", 61 + "serde_bytes", 62 + "unsigned-varint", 63 + ] 64 + 65 + [[package]] 66 + name = "const-str" 67 + version = "0.4.3" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 70 + 71 + [[package]] 72 + name = "core2" 73 + version = "0.4.0" 74 + source = "registry+https://github.com/rust-lang/crates.io-index" 75 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 76 + dependencies = [ 77 + "memchr", 78 + ] 79 + 80 + [[package]] 81 + name = "cpufeatures" 82 + version = "0.2.17" 83 + source = "registry+https://github.com/rust-lang/crates.io-index" 84 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 85 + dependencies = [ 86 + "libc", 87 + ] 88 + 89 + [[package]] 90 + name = "crypto-common" 91 + version = "0.1.7" 92 + source = "registry+https://github.com/rust-lang/crates.io-index" 93 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 94 + dependencies = [ 95 + "generic-array", 96 + "typenum", 97 + ] 98 + 99 + [[package]] 100 + name = "data-encoding" 101 + version = "2.10.0" 102 + source = "registry+https://github.com/rust-lang/crates.io-index" 103 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 104 + 105 + [[package]] 106 + name = "data-encoding-macro" 107 + version = "0.1.19" 108 + source = "registry+https://github.com/rust-lang/crates.io-index" 109 + checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 110 + dependencies = [ 111 + "data-encoding", 112 + "data-encoding-macro-internal", 113 + ] 114 + 115 + [[package]] 116 + name = "data-encoding-macro-internal" 117 + version = "0.1.17" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 120 + dependencies = [ 121 + "data-encoding", 122 + "syn", 123 + ] 124 + 125 + [[package]] 126 + name = "digest" 127 + version = "0.10.7" 128 + source = "registry+https://github.com/rust-lang/crates.io-index" 129 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 130 + dependencies = [ 131 + "block-buffer", 132 + "crypto-common", 133 + ] 134 + 135 + [[package]] 136 + name = "futures" 137 + version = "0.3.31" 138 + source = "registry+https://github.com/rust-lang/crates.io-index" 139 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 140 + dependencies = [ 141 + "futures-channel", 142 + "futures-core", 143 + "futures-executor", 144 + "futures-io", 145 + "futures-sink", 146 + "futures-task", 147 + "futures-util", 148 + ] 149 + 150 + [[package]] 151 + name = "futures-channel" 152 + version = "0.3.31" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 155 + dependencies = [ 156 + "futures-core", 157 + "futures-sink", 158 + ] 159 + 160 + [[package]] 161 + name = "futures-core" 162 + version = "0.3.31" 163 + source = "registry+https://github.com/rust-lang/crates.io-index" 164 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 165 + 166 + [[package]] 167 + name = "futures-executor" 168 + version = "0.3.31" 169 + source = "registry+https://github.com/rust-lang/crates.io-index" 170 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 171 + dependencies = [ 172 + "futures-core", 173 + "futures-task", 174 + "futures-util", 175 + ] 176 + 177 + [[package]] 178 + name = "futures-io" 179 + version = "0.3.31" 180 + source = "registry+https://github.com/rust-lang/crates.io-index" 181 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 182 + 183 + [[package]] 184 + name = "futures-macro" 185 + version = "0.3.31" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 188 + dependencies = [ 189 + "proc-macro2", 190 + "quote", 191 + "syn", 192 + ] 193 + 194 + [[package]] 195 + name = "futures-sink" 196 + version = "0.3.31" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 199 + 200 + [[package]] 201 + name = "futures-task" 202 + version = "0.3.31" 203 + source = "registry+https://github.com/rust-lang/crates.io-index" 204 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 205 + 206 + [[package]] 207 + name = "futures-util" 208 + version = "0.3.31" 209 + source = "registry+https://github.com/rust-lang/crates.io-index" 210 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 211 + dependencies = [ 212 + "futures-channel", 213 + "futures-core", 214 + "futures-io", 215 + "futures-macro", 216 + "futures-sink", 217 + "futures-task", 218 + "memchr", 219 + "pin-project-lite", 220 + "pin-utils", 221 + "slab", 222 + ] 223 + 224 + [[package]] 225 + name = "generic-array" 226 + version = "0.14.7" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 229 + dependencies = [ 230 + "typenum", 231 + "version_check", 232 + ] 233 + 234 + [[package]] 235 + name = "ipld-core" 236 + version = "0.4.2" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 239 + dependencies = [ 240 + "cid", 241 + "serde", 242 + "serde_bytes", 243 + ] 244 + 245 + [[package]] 246 + name = "leb128" 247 + version = "0.2.5" 248 + source = "registry+https://github.com/rust-lang/crates.io-index" 249 + checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 250 + 251 + [[package]] 252 + name = "libc" 253 + version = "0.2.180" 254 + source = "registry+https://github.com/rust-lang/crates.io-index" 255 + checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 256 + 257 + [[package]] 258 + name = "match-lookup" 259 + version = "0.1.2" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 262 + dependencies = [ 263 + "proc-macro2", 264 + "quote", 265 + "syn", 266 + ] 267 + 268 + [[package]] 269 + name = "memchr" 270 + version = "2.7.6" 271 + source = "registry+https://github.com/rust-lang/crates.io-index" 272 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 273 + 274 + [[package]] 275 + name = "multibase" 276 + version = "0.9.2" 277 + source = "registry+https://github.com/rust-lang/crates.io-index" 278 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 279 + dependencies = [ 280 + "base-x", 281 + "base256emoji", 282 + "data-encoding", 283 + "data-encoding-macro", 284 + ] 285 + 286 + [[package]] 287 + name = "multihash" 288 + version = "0.19.3" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 291 + dependencies = [ 292 + "core2", 293 + "serde", 294 + "unsigned-varint", 295 + ] 296 + 297 + [[package]] 298 + name = "pin-project-lite" 299 + version = "0.2.16" 300 + source = "registry+https://github.com/rust-lang/crates.io-index" 301 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 302 + 303 + [[package]] 304 + name = "pin-utils" 305 + version = "0.1.0" 306 + source = "registry+https://github.com/rust-lang/crates.io-index" 307 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 308 + 309 + [[package]] 310 + name = "proc-macro2" 311 + version = "1.0.106" 312 + source = "registry+https://github.com/rust-lang/crates.io-index" 313 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 314 + dependencies = [ 315 + "unicode-ident", 316 + ] 317 + 318 + [[package]] 319 + name = "quote" 320 + version = "1.0.43" 321 + source = "registry+https://github.com/rust-lang/crates.io-index" 322 + checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" 323 + dependencies = [ 324 + "proc-macro2", 325 + ] 326 + 327 + [[package]] 328 + name = "scopeguard" 329 + version = "1.2.0" 330 + source = "registry+https://github.com/rust-lang/crates.io-index" 331 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 332 + 333 + [[package]] 334 + name = "serde" 335 + version = "1.0.228" 336 + source = "registry+https://github.com/rust-lang/crates.io-index" 337 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 338 + dependencies = [ 339 + "serde_core", 340 + "serde_derive", 341 + ] 342 + 343 + [[package]] 344 + name = "serde_bytes" 345 + version = "0.11.19" 346 + source = "registry+https://github.com/rust-lang/crates.io-index" 347 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 348 + dependencies = [ 349 + "serde", 350 + "serde_core", 351 + ] 352 + 353 + [[package]] 354 + name = "serde_core" 355 + version = "1.0.228" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 358 + dependencies = [ 359 + "serde_derive", 360 + ] 361 + 362 + [[package]] 363 + name = "serde_derive" 364 + version = "1.0.228" 365 + source = "registry+https://github.com/rust-lang/crates.io-index" 366 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 367 + dependencies = [ 368 + "proc-macro2", 369 + "quote", 370 + "syn", 371 + ] 372 + 373 + [[package]] 374 + name = "serde_ipld_dagcbor" 375 + version = "0.6.4" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 378 + dependencies = [ 379 + "cbor4ii", 380 + "ipld-core", 381 + "scopeguard", 382 + "serde", 383 + ] 384 + 385 + [[package]] 386 + name = "sha2" 387 + version = "0.10.9" 388 + source = "registry+https://github.com/rust-lang/crates.io-index" 389 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 390 + dependencies = [ 391 + "cfg-if", 392 + "cpufeatures", 393 + "digest", 394 + ] 395 + 396 + [[package]] 397 + name = "slab" 398 + version = "0.4.11" 399 + source = "registry+https://github.com/rust-lang/crates.io-index" 400 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 401 + 402 + [[package]] 403 + name = "star" 404 + version = "0.1.0" 405 + dependencies = [ 406 + "bytes", 407 + "cid", 408 + "futures", 409 + "leb128", 410 + "serde", 411 + "serde_bytes", 412 + "serde_ipld_dagcbor", 413 + "sha2", 414 + "thiserror", 415 + "tokio", 416 + "tokio-util", 417 + ] 418 + 419 + [[package]] 420 + name = "syn" 421 + version = "2.0.114" 422 + source = "registry+https://github.com/rust-lang/crates.io-index" 423 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 424 + dependencies = [ 425 + "proc-macro2", 426 + "quote", 427 + "unicode-ident", 428 + ] 429 + 430 + [[package]] 431 + name = "thiserror" 432 + version = "2.0.18" 433 + source = "registry+https://github.com/rust-lang/crates.io-index" 434 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 435 + dependencies = [ 436 + "thiserror-impl", 437 + ] 438 + 439 + [[package]] 440 + name = "thiserror-impl" 441 + version = "2.0.18" 442 + source = "registry+https://github.com/rust-lang/crates.io-index" 443 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 444 + dependencies = [ 445 + "proc-macro2", 446 + "quote", 447 + "syn", 448 + ] 449 + 450 + [[package]] 451 + name = "tokio" 452 + version = "1.49.0" 453 + source = "registry+https://github.com/rust-lang/crates.io-index" 454 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 455 + dependencies = [ 456 + "bytes", 457 + "pin-project-lite", 458 + "tokio-macros", 459 + ] 460 + 461 + [[package]] 462 + name = "tokio-macros" 463 + version = "2.6.0" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 466 + dependencies = [ 467 + "proc-macro2", 468 + "quote", 469 + "syn", 470 + ] 471 + 472 + [[package]] 473 + name = "tokio-util" 474 + version = "0.7.18" 475 + source = "registry+https://github.com/rust-lang/crates.io-index" 476 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 477 + dependencies = [ 478 + "bytes", 479 + "futures-core", 480 + "futures-sink", 481 + "pin-project-lite", 482 + "tokio", 483 + ] 484 + 485 + [[package]] 486 + name = "typenum" 487 + version = "1.19.0" 488 + source = "registry+https://github.com/rust-lang/crates.io-index" 489 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 490 + 491 + [[package]] 492 + name = "unicode-ident" 493 + version = "1.0.22" 494 + source = "registry+https://github.com/rust-lang/crates.io-index" 495 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 496 + 497 + [[package]] 498 + name = "unsigned-varint" 499 + version = "0.8.0" 500 + source = "registry+https://github.com/rust-lang/crates.io-index" 501 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 502 + 503 + [[package]] 504 + name = "version_check" 505 + version = "0.9.5" 506 + source = "registry+https://github.com/rust-lang/crates.io-index" 507 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+25
Cargo.toml
··· 1 + [package] 2 + name = "star" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [features] 7 + default = ["async"] 8 + blocking = [] 9 + async = ["dep:tokio", "dep:tokio-util", "dep:futures"] 10 + 11 + [dependencies] 12 + # Core 13 + serde = { version = "1", features = ["derive"] } 14 + serde_ipld_dagcbor = "0.6" 15 + serde_bytes = "0.11" 16 + cid = { version = "0.11", features = ["serde-codec"] } 17 + leb128 = "0.2" 18 + thiserror = "2" 19 + sha2 = "0.10" 20 + bytes = "1" 21 + 22 + # Async 23 + tokio = { version = "1", features = ["io-util", "macros", "rt"], optional = true } 24 + tokio-util = { version = "0.7", features = ["codec"], optional = true } 25 + futures = { version = "0.3", optional = true }
+14
src/async_io.rs
··· 1 + use crate::error::StarError; 2 + use crate::parser::StarParser; 3 + use crate::types::StarItem; 4 + use bytes::BytesMut; 5 + use tokio_util::codec::Decoder; 6 + 7 + impl Decoder for StarParser { 8 + type Item = StarItem; 9 + type Error = StarError; 10 + 11 + fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { 12 + self.input(src) 13 + } 14 + }
+71
src/blocking.rs
··· 1 + use crate::error::Result; 2 + use crate::parser::StarParser; 3 + use crate::types::{Commit, StarItem}; 4 + use bytes::BytesMut; 5 + use cid::Cid; 6 + use std::io::Read; 7 + 8 + pub struct StarIterator<R> { 9 + reader: R, 10 + parser: StarParser, 11 + buffer: BytesMut, 12 + } 13 + 14 + impl<R: Read> StarIterator<R> { 15 + pub fn new(reader: R) -> Self { 16 + Self { 17 + reader, 18 + parser: StarParser::new(), 19 + buffer: BytesMut::new(), 20 + } 21 + } 22 + 23 + /// Returns an iterator over all items (Nodes, Records, Header) in the tree (order preserved). 24 + /// This is the identity iterator. 25 + pub fn iter_tree(self) -> Self { 26 + self 27 + } 28 + 29 + /// Returns an iterator over triples of (key, cid, Option<record>). 30 + /// Skips Commit header and MST Nodes. 31 + pub fn iter(self) -> impl Iterator<Item = Result<(Vec<u8>, Cid, Option<Vec<u8>>)>> { 32 + self.filter_map(|res| match res { 33 + Ok(StarItem::Record { key, cid, content }) => Some(Ok((key, cid, content))), 34 + Ok(_) => None, 35 + Err(e) => Some(Err(e)), 36 + }) 37 + } 38 + 39 + /// Returns an iterator over (key, cid) pairs. 40 + pub fn iter_keys(self) -> impl Iterator<Item = Result<(Vec<u8>, Cid)>> { 41 + self.filter_map(|res| match res { 42 + Ok(StarItem::Record { key, cid, .. }) => Some(Ok((key, cid))), 43 + Ok(_) => None, 44 + Err(e) => Some(Err(e)), 45 + }) 46 + } 47 + } 48 + 49 + impl<R: Read> Iterator for StarIterator<R> { 50 + type Item = Result<StarItem>; 51 + 52 + fn next(&mut self) -> Option<Self::Item> { 53 + loop { 54 + match self.parser.input(&mut self.buffer) { 55 + Ok(Some(item)) => return Some(Ok(item)), 56 + Ok(None) => { 57 + if self.buffer.capacity() == 0 { 58 + self.buffer.reserve(8192); 59 + } 60 + let mut temp = [0u8; 8192]; 61 + match self.reader.read(&mut temp) { 62 + Ok(0) => return None, // EOF 63 + Ok(n) => self.buffer.extend_from_slice(&temp[..n]), 64 + Err(e) => return Some(Err(e.into())), 65 + } 66 + } 67 + Err(e) => return Some(Err(e)), 68 + } 69 + } 70 + } 71 + }
+26
src/error.rs
··· 1 + use std::io; 2 + use thiserror::Error; 3 + 4 + #[derive(Error, Debug)] 5 + pub enum StarError { 6 + #[error("IO error: {0}")] 7 + Io(#[from] io::Error), 8 + #[error("CBOR error: {0}")] 9 + Cbor(String), 10 + #[error("CID error: {0}")] 11 + Cid(#[from] cid::Error), 12 + #[error("Multihash error: {0}")] 13 + Multihash(#[from] cid::multihash::Error), 14 + #[error("Invalid STAR header")] 15 + InvalidHeader, 16 + #[error("Unexpected EOF")] 17 + UnexpectedEof, 18 + #[error("Verification failed: expected {expected}, got {computed}")] 19 + VerificationFailed { expected: String, computed: String }, 20 + #[error("Invalid state: {0}")] 21 + InvalidState(String), 22 + #[error("Trailing data")] 23 + TrailingData, 24 + } 25 + 26 + pub type Result<T> = std::result::Result<T, StarError>;
+18
src/lib.rs
··· 1 + pub mod error; 2 + pub mod parser; 3 + pub mod ser; 4 + pub mod types; 5 + 6 + #[cfg(feature = "blocking")] 7 + pub mod blocking; 8 + 9 + #[cfg(feature = "async")] 10 + pub mod async_io; 11 + 12 + pub use error::{Result, StarError}; 13 + pub use parser::StarParser; 14 + pub use ser::StarEncoder; 15 + pub use types::{Commit, MstEntry, MstNode, StarItem}; 16 + 17 + #[cfg(feature = "blocking")] 18 + pub use blocking::StarIterator;
+286
src/parser.rs
··· 1 + use crate::error::{Result, StarError}; 2 + use crate::types::{Commit, MstNode, StarItem}; 3 + use bytes::{Buf, BytesMut}; 4 + use cid::Cid; 5 + use sha2::{Digest, Sha256}; 6 + use std::io::Cursor; 7 + 8 + #[derive(Debug)] 9 + enum State { 10 + Header, 11 + Body { 12 + stack: Vec<StackItem>, 13 + current_len: Option<usize>, 14 + }, 15 + Done, 16 + } 17 + 18 + #[derive(Debug)] 19 + enum StackItem { 20 + Node { 21 + expected: Option<Cid>, 22 + }, 23 + Record { 24 + key: Vec<u8>, 25 + expected: Option<Cid>, 26 + implicit_index: Option<usize>, 27 + }, 28 + VerifyLayer0 { 29 + node: MstNode, 30 + parent_expected: Option<Cid>, 31 + pending_records: Vec<(usize, Cid)>, 32 + }, 33 + } 34 + 35 + pub struct StarParser { 36 + state: State, 37 + } 38 + 39 + impl StarParser { 40 + pub fn new() -> Self { 41 + Self { 42 + state: State::Header, 43 + } 44 + } 45 + 46 + pub fn input(&mut self, buf: &mut BytesMut) -> Result<Option<StarItem>> { 47 + loop { 48 + match self.state { 49 + State::Done => return Ok(None), 50 + State::Header => { 51 + if buf.is_empty() { 52 + return Ok(None); 53 + } 54 + if buf[0] != 0x2A { 55 + return Err(StarError::InvalidHeader); 56 + } 57 + 58 + let mut cursor = Cursor::new(&buf[1..]); 59 + 60 + let _ver = match leb128::read::unsigned(&mut cursor) { 61 + Ok(v) => v, 62 + Err(_) => return Ok(None), 63 + }; 64 + 65 + let len = match leb128::read::unsigned(&mut cursor) { 66 + Ok(l) => l, 67 + Err(_) => return Ok(None), 68 + }; 69 + 70 + let header_len = 1 + cursor.position() as usize; 71 + let total_len = header_len + len as usize; 72 + 73 + if buf.len() < total_len { 74 + return Ok(None); 75 + } 76 + 77 + buf.advance(header_len); 78 + let commit_bytes = buf.split_to(len as usize); 79 + let commit: Commit = serde_ipld_dagcbor::from_slice(&commit_bytes) 80 + .map_err(|e| StarError::Cbor(e.to_string()))?; 81 + 82 + self.state = State::Body { 83 + stack: commit 84 + .data 85 + .map(|root_cid| { 86 + vec![StackItem::Node { 87 + expected: Some(root_cid), 88 + }] 89 + }) 90 + .unwrap_or(vec![]), 91 + current_len: None, 92 + }; 93 + return Ok(Some(StarItem::Commit(commit))); 94 + } 95 + State::Body { 96 + ref mut stack, 97 + mut current_len, 98 + } => { 99 + if stack.is_empty() { 100 + self.state = State::Done; 101 + return Ok(None); 102 + } 103 + 104 + // Check for verification task 105 + if let Some(StackItem::VerifyLayer0 { .. }) = stack.last() 106 + && let Some(StackItem::VerifyLayer0 { 107 + mut node, 108 + parent_expected, 109 + pending_records, 110 + }) = stack.pop() 111 + { 112 + for (idx, cid) in pending_records { 113 + if idx < node.e.len() { 114 + node.e[idx].v = Some(cid); 115 + } 116 + } 117 + 118 + let bytes = serde_ipld_dagcbor::to_vec(&node) 119 + .map_err(|e| StarError::Cbor(e.to_string()))?; 120 + let hash = Sha256::digest(&bytes); 121 + // CIDv1 DAG-CBOR SHA2-256 122 + let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 123 + 124 + if let Some(expected) = parent_expected 125 + && cid != expected 126 + { 127 + return Err(StarError::VerificationFailed { 128 + expected: expected.to_string(), 129 + computed: cid.to_string(), 130 + }); 131 + } 132 + continue; 133 + } 134 + 135 + // Read Length 136 + let len = if let Some(l) = current_len { l } else { 137 + let mut cursor = Cursor::new(&buf[..]); 138 + match leb128::read::unsigned(&mut cursor) { 139 + Ok(l) => { 140 + current_len.replace(l as usize); 141 + buf.advance(cursor.position() as usize); 142 + l as usize 143 + } 144 + Err(_) => return Ok(None), 145 + } 146 + }; 147 + 148 + if buf.len() < len { 149 + return Ok(None); 150 + } 151 + 152 + let block_bytes = buf.split_to(len); 153 + current_len.take(); 154 + 155 + match stack.pop().unwrap() { 156 + StackItem::Node { expected } => { 157 + let node: MstNode = serde_ipld_dagcbor::from_slice(&block_bytes) 158 + .map_err(|e| StarError::Cbor(e.to_string()))?; 159 + 160 + // Check for implicit records 161 + let mut has_implicit = false; 162 + for e in &node.e { 163 + if e.v_archived == Some(true) && e.v.is_none() { 164 + has_implicit = true; 165 + break; 166 + } 167 + } 168 + 169 + if !has_implicit { 170 + let hash = Sha256::digest(&block_bytes); 171 + let cid = Cid::new_v1( 172 + 0x71, 173 + cid::multihash::Multihash::wrap(0x12, &hash)?, 174 + ); 175 + if let Some(exp) = expected 176 + && cid != exp 177 + { 178 + return Err(StarError::VerificationFailed { 179 + expected: exp.to_string(), 180 + computed: cid.to_string(), 181 + }); 182 + } 183 + } else { 184 + stack.push(StackItem::VerifyLayer0 { 185 + node: node.clone(), 186 + parent_expected: expected, 187 + pending_records: Vec::new(), 188 + }); 189 + } 190 + 191 + // Reconstruct keys 192 + let mut prev_key_bytes = Vec::new(); 193 + let mut entry_keys = Vec::new(); 194 + for e in &node.e { 195 + let mut key = if e.p as usize <= prev_key_bytes.len() { 196 + prev_key_bytes[..e.p as usize].to_vec() 197 + } else { 198 + // Should not happen in valid MST 199 + prev_key_bytes.clone() 200 + }; 201 + key.extend_from_slice(&e.k); 202 + entry_keys.push(key.clone()); 203 + prev_key_bytes = key; 204 + } 205 + 206 + // Push children in reverse 207 + for i in (0..node.e.len()).rev() { 208 + let e = &node.e[i]; 209 + let key = entry_keys[i].clone(); 210 + 211 + if e.t_archived == Some(true) { 212 + stack.push(StackItem::Node { expected: e.t }); 213 + } 214 + 215 + if e.v_archived == Some(true) { 216 + let implicit_index = if e.v.is_none() { Some(i) } else { None }; 217 + stack.push(StackItem::Record { 218 + key, 219 + expected: e.v, 220 + implicit_index, 221 + }); 222 + } 223 + } 224 + 225 + if node.l_archived == Some(true) { 226 + stack.push(StackItem::Node { expected: node.l }); 227 + } 228 + 229 + return Ok(Some(StarItem::Node(node))); 230 + } 231 + StackItem::Record { 232 + key, 233 + expected, 234 + implicit_index, 235 + } => { 236 + let hash = Sha256::digest(&block_bytes); 237 + let cid = 238 + Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 239 + 240 + if let Some(exp) = expected 241 + && cid != exp 242 + { 243 + return Err(StarError::VerificationFailed { 244 + expected: exp.to_string(), 245 + computed: cid.to_string(), 246 + }); 247 + } 248 + 249 + if let Some(idx) = implicit_index { 250 + let mut found = false; 251 + for item in stack.iter_mut().rev() { 252 + if let StackItem::VerifyLayer0 { 253 + pending_records, .. 254 + } = item 255 + { 256 + pending_records.push((idx, cid)); 257 + found = true; 258 + break; 259 + } 260 + } 261 + if !found { 262 + return Err(StarError::InvalidState( 263 + "Implicit record verification context missing".into(), 264 + )); 265 + } 266 + } 267 + 268 + return Ok(Some(StarItem::Record { 269 + key, 270 + cid, 271 + content: Some(block_bytes.to_vec()), 272 + })); 273 + } 274 + _ => return Err(StarError::InvalidState("Unexpected stack item".into())), 275 + } 276 + } 277 + } 278 + } 279 + } 280 + } 281 + 282 + impl Default for StarParser { 283 + fn default() -> Self { 284 + Self::new() 285 + } 286 + }
+70
src/ser.rs
··· 1 + use crate::error::Result; 2 + use crate::types::{Commit, MstNode, StarItem}; 3 + use bytes::{BufMut, BytesMut}; 4 + 5 + pub struct StarEncoder; 6 + 7 + impl StarEncoder { 8 + pub fn write_header(commit: &Commit, dst: &mut BytesMut) -> Result<()> { 9 + dst.put_u8(0x2A); 10 + 11 + let mut buf = Vec::new(); 12 + leb128::write::unsigned(&mut buf, 1)?; 13 + dst.extend_from_slice(&buf); 14 + 15 + let commit_bytes = serde_ipld_dagcbor::to_vec(commit) 16 + .map_err(|e| crate::error::StarError::Cbor(e.to_string()))?; 17 + 18 + let mut buf = Vec::new(); 19 + leb128::write::unsigned(&mut buf, commit_bytes.len() as u64)?; 20 + dst.extend_from_slice(&buf); 21 + 22 + dst.extend_from_slice(&commit_bytes); 23 + 24 + Ok(()) 25 + } 26 + 27 + pub fn write_node(node: &MstNode, dst: &mut BytesMut) -> Result<()> { 28 + let node_bytes = serde_ipld_dagcbor::to_vec(node) 29 + .map_err(|e| crate::error::StarError::Cbor(e.to_string()))?; 30 + 31 + let mut buf = Vec::new(); 32 + leb128::write::unsigned(&mut buf, node_bytes.len() as u64)?; 33 + dst.extend_from_slice(&buf); 34 + 35 + dst.extend_from_slice(&node_bytes); 36 + 37 + Ok(()) 38 + } 39 + 40 + pub fn write_record(record_bytes: &[u8], dst: &mut BytesMut) -> Result<()> { 41 + let mut buf = Vec::new(); 42 + leb128::write::unsigned(&mut buf, record_bytes.len() as u64)?; 43 + dst.extend_from_slice(&buf); 44 + 45 + dst.extend_from_slice(record_bytes); 46 + 47 + Ok(()) 48 + } 49 + } 50 + 51 + #[cfg(feature = "async")] 52 + impl tokio_util::codec::Encoder<StarItem> for StarEncoder { 53 + type Error = crate::error::StarError; 54 + 55 + fn encode(&mut self, item: StarItem, dst: &mut BytesMut) -> Result<()> { 56 + match item { 57 + StarItem::Commit(c) => Self::write_header(&c, dst), 58 + StarItem::Node(n) => Self::write_node(&n, dst), 59 + StarItem::Record { content, .. } => { 60 + if let Some(bytes) = content { 61 + Self::write_record(&bytes, dst) 62 + } else { 63 + Err(crate::error::StarError::InvalidState( 64 + "Cannot serialize record without content".into(), 65 + )) 66 + } 67 + } 68 + } 69 + } 70 + }
+62
src/types.rs
··· 1 + use cid::Cid; 2 + use serde::{Deserialize, Serialize}; 3 + use serde_bytes::ByteBuf; 4 + 5 + /// The ATProto Commit object (DAG-CBOR) 6 + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 7 + pub struct Commit { 8 + pub did: String, 9 + pub version: u64, 10 + #[serde(default, skip_serializing_if = "Option::is_none")] 11 + pub data: Option<Cid>, 12 + pub rev: String, 13 + #[serde(default, skip_serializing_if = "Option::is_none")] 14 + pub prev: Option<Cid>, 15 + #[serde(default, skip_serializing_if = "Option::is_none")] 16 + pub sig: Option<ByteBuf>, 17 + } 18 + 19 + /// The MST Node object (DAG-CBOR) 20 + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 21 + pub struct MstNode { 22 + #[serde(default, skip_serializing_if = "Option::is_none")] 23 + pub l: Option<Cid>, 24 + 25 + #[serde(rename = "L", default, skip_serializing_if = "Option::is_none")] 26 + pub l_archived: Option<bool>, 27 + 28 + pub e: Vec<MstEntry>, 29 + } 30 + 31 + /// The MST Entry object 32 + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 33 + pub struct MstEntry { 34 + /// Prefix length 35 + pub p: u32, 36 + /// Key suffix 37 + pub k: ByteBuf, 38 + /// Value (Record CID) - Optional for implicit records 39 + #[serde(default, skip_serializing_if = "Option::is_none")] 40 + pub v: Option<Cid>, 41 + /// Value Archived Flag 42 + #[serde(rename = "V", default, skip_serializing_if = "Option::is_none")] 43 + pub v_archived: Option<bool>, 44 + /// Right Subtree CID 45 + #[serde(default, skip_serializing_if = "Option::is_none")] 46 + pub t: Option<Cid>, 47 + /// Right Subtree Archived Flag 48 + #[serde(rename = "T", default, skip_serializing_if = "Option::is_none")] 49 + pub t_archived: Option<bool>, 50 + } 51 + 52 + /// A parsed item from the STAR stream 53 + #[derive(Debug, Clone)] 54 + pub enum StarItem { 55 + Commit(Commit), 56 + Node(MstNode), 57 + Record { 58 + key: Vec<u8>, 59 + cid: Cid, 60 + content: Option<Vec<u8>>, 61 + }, 62 + }