Skip to content

Commit

Permalink
Merge pull request #123 from n0-computer/cid
Browse files Browse the repository at this point in the history
Add newtype wrapper for Hash that is compatible with Cid
  • Loading branch information
rklaehn authored Feb 7, 2023
2 parents 13f703f + 2e3d577 commit 1a127fa
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 13 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ blake3 = "1.3.3"
bytes = "1"
clap = { version = "4", features = ["derive"], optional = true }
console = { version = "0.15.5", optional = true }
data-encoding = { version = "2.3.3", optional = true }
der = { version = "0.6", features = ["alloc", "derive"] }
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
futures = "0.3.25"
indicatif = { version = "0.17", features = ["tokio"], optional = true }
multibase = { version = "0.9.1", optional = true }
postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"] }
quinn = "0.9.3"
rand = "0.7"
Expand Down Expand Up @@ -49,7 +51,7 @@ testdir = "0.7.1"

[features]
default = ["cli"]
cli = ["clap", "console", "indicatif"]
cli = ["clap", "console", "indicatif", "data-encoding", "multibase"]

[[bin]]
name = "sendme"
Expand Down
101 changes: 96 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
use std::{fmt, net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
Expand All @@ -10,7 +10,7 @@ use sendme::protocol::AuthToken;
use sendme::provider::Ticket;
use tokio::io::AsyncWriteExt;
use tokio::sync::Mutex;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use tracing_subscriber::{prelude::*, EnvFilter};

use sendme::{get, provider, Hash, Keypair, PeerId};

Expand Down Expand Up @@ -44,7 +44,7 @@ enum Commands {
#[clap(about = "Fetch the data from the hash")]
Get {
/// The root hash to retrieve.
hash: Hash,
hash: Blake3Cid,
/// PeerId of the provider.
#[clap(long, short)]
peer: PeerId,
Expand Down Expand Up @@ -101,13 +101,98 @@ impl OutWriter {
}
}

#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Blake3Cid(Hash);

const CID_PREFIX: [u8; 4] = [
0x01, // version
0x55, // raw codec
0x1e, // hash function, blake3
0x20, // hash size, 32 bytes
];

impl Blake3Cid {
pub fn new(hash: Hash) -> Self {
Blake3Cid(hash)
}

pub fn as_hash(&self) -> &Hash {
&self.0
}

pub fn as_bytes(&self) -> [u8; 36] {
let hash: [u8; 32] = self.0.as_ref().try_into().unwrap();
let mut res = [0u8; 36];
res[0..4].copy_from_slice(&CID_PREFIX);
res[4..36].copy_from_slice(&hash);
res
}

pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
anyhow::ensure!(
bytes.len() == 36,
"invalid cid length, expected 36, got {}",
bytes.len()
);
anyhow::ensure!(bytes[0..4] == CID_PREFIX, "invalid cid prefix");
let mut hash = [0u8; 32];
hash.copy_from_slice(&bytes[4..36]);
Ok(Blake3Cid(Hash::from(hash)))
}
}

impl fmt::Display for Blake3Cid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// result will be 58 bytes plus prefix
let mut res = [b'b'; 59];
// write the encoded bytes
data_encoding::BASE32_NOPAD.encode_mut(&self.as_bytes(), &mut res[1..]);
// convert to string, this is guaranteed to succeed
let t = std::str::from_utf8_mut(res.as_mut()).unwrap();
// hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
t.make_ascii_lowercase();
// write the str, no allocations
f.write_str(t)
}
}

impl FromStr for Blake3Cid {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let sb = s.as_bytes();
if sb.len() == 59 && sb[0] == b'b' {
// this is a base32 encoded cid, we can decode it directly
let mut t = [0u8; 58];
t.copy_from_slice(&sb[1..]);
// hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
std::str::from_utf8_mut(t.as_mut())
.unwrap()
.make_ascii_uppercase();
// decode the bytes
let mut res = [0u8; 36];
data_encoding::BASE32_NOPAD
.decode_mut(&t, &mut res)
.map_err(|_e| anyhow::anyhow!("invalid base32"))?;
// convert to cid, this will check the prefix
Self::from_bytes(&res)
} else {
// if we want to support all the weird multibase prefixes, we have no choice
// but to use the multibase crate
let (_base, bytes) = multibase::decode(s)?;
Self::from_bytes(bytes.as_ref())
}
}
}

const PROGRESS_STYLE: &str =
"{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})";

#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
tracing_subscriber::registry()
.with(fmt::layer().with_writer(std::io::stderr))
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.with(EnvFilter::from_default_env())
.init();

Expand All @@ -130,7 +215,7 @@ async fn main() -> Result<()> {
}
let token =
AuthToken::from_str(&token).context("Wrong format for authentication token")?;
get_interactive(hash, opts, token, out).await?;
get_interactive(*hash.as_hash(), opts, token, out).await?;
}
Commands::GetTicket { out, ticket } => {
let Ticket {
Expand Down Expand Up @@ -185,6 +270,12 @@ async fn main() -> Result<()> {
};

let (db, hash) = provider::create_collection(sources).await?;

println!("Collection: {}\n", Blake3Cid::new(hash));
for (_, path, size) in db.blobs() {
println!("- {}: {} bytes", path.display(), size);
}
println!();
let mut builder = provider::Provider::builder(db).keypair(keypair);
if let Some(addr) = addr {
builder = builder.bind_addr(addr);
Expand Down
18 changes: 11 additions & 7 deletions src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ impl Database {
fn get(&self, key: &Hash) -> Option<&BlobOrCollection> {
self.0.get(key)
}

/// Iterate over all blobs in the database.
pub fn blobs(&self) -> impl Iterator<Item = (&Hash, &PathBuf, u64)> + '_ {
self.0
.iter()
.filter_map(|(k, v)| match v {
BlobOrCollection::Blob(data) => Some((k, data)),
BlobOrCollection::Collection(_) => None,
})
.map(|(k, data)| (k, &data.path, data.size))
}
}

/// Builder for the [`Provider`].
Expand Down Expand Up @@ -635,13 +646,6 @@ pub async fn create_collection(data_sources: Vec<DataSource>) -> Result<(Databas
let data = postcard::to_slice(&c, &mut buffer)?;
let (outboard, hash) = bao::encode::outboard(&data);
let hash = Hash::from(hash);
println!("Collection: {hash}\n");
for el in db.values() {
if let BlobOrCollection::Blob(blob) = el {
println!("- {}: {} bytes", blob.path.display(), blob.size);
}
}
println!();
db.insert(
hash,
BlobOrCollection::Collection((Bytes::from(outboard), Bytes::from(data.to_vec()))),
Expand Down

0 comments on commit 1a127fa

Please sign in to comment.