Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
7.2 kB · 231 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232pub mod blocks;pub mod chain;pub mod comment;pub mod git_ref;pub mod git_refs;pub mod issue;pub mod label;pub mod mst;pub mod proof;pub mod reaction;
use bytes::Bytes;use cid::Cid;use knot_types::{RecordBody, RecordBodyError, UnixSeconds};use serde::Serialize;#[derive(Debug, thiserror::Error)]pub enum RecordError { #[error("atproto block error: {0}")] Block(#[from] jacquard_repo::RepoError), #[error("covering proof failed to invert the commit cold")] ColdInversion, #[error("malformed atproto record: {0}")] Malformed(String), #[error("createdAt {0} is outside the RFC 3339 range")] CreatedAtUnrenderable(i64),}
pub type Result<T, E = RecordError> = std::result::Result<T, E>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]#[serde(transparent)]pub struct CreatedAt(String);
impl CreatedAt { pub fn at(time: UnixSeconds) -> Result<Self, RecordError> { chrono::DateTime::from_timestamp(time.get(), 0) .map(|at| Self(at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))) .ok_or(RecordError::CreatedAtUnrenderable(time.get())) }}
pub(crate) fn dag_cbor<T: Serialize>(value: &T) -> Vec<u8> { serde_ipld_dagcbor::to_vec(value).expect("Record text fields and lists encode as dag-cbor")}
pub(crate) fn encoded<T: Serialize>(value: &T) -> Result<RecordBody, RecordBodyError> { RecordBody::new(dag_cbor(value))}
#[cfg(test)]pub(crate) fn frozen(hex: &str) -> Vec<u8> { hex.as_bytes() .as_chunks::<2>() .0 .iter() .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap()) .collect()}
pub fn cid_of(block: &[u8]) -> Cid { knot_types::dag_cbor_cid(block)}
pub fn car_bytes(root: Cid, blocks: &std::collections::BTreeMap<Cid, Bytes>) -> Vec<u8> { let header = iroh_car::CarHeader::new_v1(vec![root]); let header_bytes = header.encode().expect("A CAR header encodes"); let mut out = Vec::new(); varint(&mut out, header_bytes.len()); out.extend_from_slice(&header_bytes); blocks.iter().for_each(|(cid, data)| { let cid_bytes = cid.to_bytes(); varint(&mut out, cid_bytes.len() + data.len()); out.extend_from_slice(&cid_bytes); out.extend_from_slice(data); }); out}
fn varint(out: &mut Vec<u8>, value: usize) { let chunks = std::iter::successors(Some(value), |value| (*value >= 0x80).then(|| value >> 7)); out.extend(chunks.map(|value| ((value & 0x7f) as u8) | u8::from(value >= 0x80) << 7));}
#[derive(Debug, thiserror::Error)]pub enum CarError { #[error("CAR is cut short at byte {0}")] Truncated(usize), #[error("CAR varint at byte {0} doesn't fit a length")] Varint(usize), #[error("CAR header doesn't decode: {0}")] Header(String), #[error("CAR section at byte {at} is missing its CID: {reason}")] Cid { at: usize, reason: String },}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct Car { pub roots: Vec<Cid>, pub blocks: std::collections::BTreeMap<Cid, Bytes>,}
struct Varint { value: usize, after: usize,}
fn read_varint(bytes: &[u8], at: usize) -> Result<Varint, CarError> { let tail = bytes.get(at..).ok_or(CarError::Truncated(at))?; let last = tail .iter() .take(10) .position(|byte| byte & 0x80 == 0) .ok_or(CarError::Truncated(at))?; let value = tail[..=last] .iter() .enumerate() .try_fold(0usize, |value, (index, byte)| { let part = usize::from(byte & 0x7f); let shift = (7 * index) as u32; part.checked_shl(shift) .filter(|shifted| shifted >> shift == part) .map(|shifted| value | shifted) }) .ok_or(CarError::Varint(at))?; Ok(Varint { value, after: at + last + 1, })}
pub fn car_blocks(bytes: &[u8]) -> Result<Car, CarError> { let framed = read_varint(bytes, 0)?; let header = bytes .get(framed.after..framed.after + framed.value) .ok_or(CarError::Truncated(framed.after))?; let roots = iroh_car::CarHeader::decode(header) .map_err(|error| CarError::Header(error.to_string()))? .roots() .to_vec(); let mut at = framed.after + framed.value; let blocks = std::iter::from_fn(|| { (at < bytes.len()).then(|| { let framed = read_varint(bytes, at)?; let section = bytes .get(framed.after..framed.after + framed.value) .ok_or(CarError::Truncated(framed.after))?; let mut cursor = std::io::Cursor::new(§ion); let cid = Cid::read_bytes(&mut cursor).map_err(|error| CarError::Cid { at: framed.after, reason: error.to_string(), })?; at = framed.after + framed.value; Ok(( cid, Bytes::copy_from_slice(§ion[cursor.position() as usize..]), )) }) }) .collect::<Result<std::collections::BTreeMap<_, _>, CarError>>()?; Ok(Car { roots, blocks })}
#[cfg(test)]mod car_tests { use super::*; use std::collections::BTreeMap;
#[tokio::test] async fn the_sync_car_writer_matches_jacquards_byte_for_byte() { let one = cid_of(b"a record body"); let two = cid_of(b"another record body"); let root = cid_of(b"a commit"); let blocks = BTreeMap::from([ (one, Bytes::from_static(b"a record body")), (two, Bytes::from_static(b"another record body")), (root, Bytes::from_static(b"a commit")), ]); assert_eq!( car_bytes(root, &blocks), jacquard_repo::car::write_car_bytes(root, blocks.clone()) .await .unwrap() ); }
#[test] fn car_reader_inverts_car_writer() { let one = cid_of(b"a record body"); let root = cid_of(b"a commit"); let blocks = BTreeMap::from([ (one, Bytes::from_static(b"a record body")), (root, Bytes::from_static(b"a commit")), ]); let written = car_bytes(root, &blocks); assert_eq!( car_blocks(&written).unwrap(), Car { roots: vec![root], blocks: blocks.clone() } ); assert_eq!( car_bytes(root, &car_blocks(&written).unwrap().blocks), written, "A read then written CAR is the same bytes" ); assert!(matches!( car_blocks(&written[..written.len() - 3]), Err(CarError::Truncated(_)) )); }}
pub struct CommitSigner<'a>(&'a dyn knot_runtime::Signer);
impl<'a> CommitSigner<'a> { pub fn new(signer: &'a dyn knot_runtime::Signer) -> Self { Self(signer) }}
impl jacquard_repo::commit::SigningKey for CommitSigner<'_> { fn sign_bytes(&self, data: &[u8]) -> jacquard_repo::Result<Bytes> { Ok(Bytes::from(self.0.sign(data).as_bytes().to_vec())) }
fn public_key(&self) -> Vec<u8> { self.0.public_key().as_bytes().to_vec() }}