diff --git a/node/src/hash.rs b/node/src/hash.rs index 7a903b55..55ef5d12 100644 --- a/node/src/hash.rs +++ b/node/src/hash.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::str::FromStr; use serde::{Deserialize, Serialize}; use sha2::{ @@ -8,11 +9,11 @@ use sha2::{ use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum ParseError { - #[error("invalid string length")] - InvalidLength, +pub enum DecodeError { #[error(transparent)] - ParseInt(#[from] std::num::ParseIntError), + Multibase(#[from] multibase::Error), + #[error("invalid digest length {0}")] + InvalidLength(usize), } /// A SHA-256 hash. @@ -25,23 +26,24 @@ impl Digest { } pub fn encode(&self) -> String { - self.to_string() + multibase::encode(multibase::Base::Base58Btc, &self.0) } - pub fn decode(s: &str) -> Result { - if s.len() != 64 { - Err(ParseError::InvalidLength) - } else { - let mut bytes: [u8; 32] = Default::default(); - for (i, byte) in (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) - .enumerate() - { - bytes[i] = byte?; - } - Ok(Self(bytes)) - } + pub fn decode(s: &str) -> Result { + let (_, bytes) = multibase::decode(s)?; + let array = bytes + .try_into() + .map_err(|v: Vec| DecodeError::InvalidLength(v.len()))?; + + Ok(Self(array)) + } +} + +impl FromStr for Digest { + type Err = DecodeError; + + fn from_str(s: &str) -> Result { + Self::decode(s) } } @@ -77,3 +79,17 @@ impl From::OutputSize>> for Digest { Self(array.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck_macros::quickcheck; + + #[quickcheck] + fn prop_encode_decode(input: Digest) { + let encoded = input.encode(); + let decoded = Digest::decode(&encoded).unwrap(); + + assert_eq!(input, decoded); + } +} diff --git a/node/src/identity.rs b/node/src/identity.rs index 284be12f..adf9ee2a 100644 --- a/node/src/identity.rs +++ b/node/src/identity.rs @@ -8,6 +8,14 @@ use thiserror::Error; use crate::hash; +#[derive(Error, Debug)] +pub enum ProjIdError { + #[error("invalid ref '{0}'")] + InvalidRef(String), + #[error("invalid digest: {0}")] + InvalidDigest(#[from] hash::DecodeError), +} + #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ProjId(hash::Digest); @@ -28,18 +36,20 @@ impl ProjId { multibase::encode(multibase::Base::Base58Btc, &self.0.as_ref()) } - pub(crate) fn from_ref(s: &str) -> Result { + pub(crate) fn from_ref(s: &str) -> Result { if let Some(s) = s.split('/').nth(2) { - let mut array: [u8; 32] = [0; 32]; - let bytes = bs58::decode(s).into(&mut array)?; - - // TODO: Multi-hash? - - assert_eq!(bytes, array.len()); - - return Ok(Self(hash::Digest::from(array))); + let id = Self::from_str(s)?; + return Ok(id); } - Err(IdError::InvalidRef(s.to_owned())) + Err(ProjIdError::InvalidRef(s.to_owned())) + } +} + +impl FromStr for ProjId { + type Err = hash::DecodeError; + + fn from_str(s: &str) -> Result { + Ok(Self(hash::Digest::from_str(s)?)) } } @@ -93,15 +103,15 @@ impl UserId { } impl FromStr for UserId { - type Err = IdError; + type Err = UserIdError; fn from_str(s: &str) -> Result { - let mut array: [u8; 32] = [0; 32]; - let bytes = bs58::decode(s).into(&mut array)?; + let (_, bytes) = multibase::decode(s)?; + let array: [u8; 32] = bytes + .try_into() + .map_err(|v: Vec| UserIdError::InvalidLength(v.len()))?; let key = VerificationKey::try_from(VerificationKeyBytes::from(array))?; - assert_eq!(bytes, array.len()); - Ok(Self(key)) } } @@ -115,11 +125,11 @@ impl Deref for UserId { } #[derive(Error, Debug)] -pub enum IdError { - #[error("invalid ref '{0}'")] - InvalidRef(String), - #[error("invalid base58 string: {0}")] - Base58(#[from] bs58::decode::Error), +pub enum UserIdError { + #[error("invalid length {0}")] + InvalidLength(usize), + #[error("invalid multibase string: {0}")] + Multibase(#[from] multibase::Error), #[error("invalid key: {0}")] InvalidKey(#[from] ed25519_consensus::Error), } @@ -178,4 +188,12 @@ mod test { assert!(!hm.insert(a)); assert!(!hm.insert(b)); } + + #[quickcheck] + fn prop_encode_decode(input: UserId) { + let encoded = input.to_string(); + let decoded = UserId::from_str(&encoded).unwrap(); + + assert_eq!(input, decoded); + } } diff --git a/node/src/storage.rs b/node/src/storage.rs index c2c0d90e..93af439b 100644 --- a/node/src/storage.rs +++ b/node/src/storage.rs @@ -13,7 +13,7 @@ pub use radicle_git_ext::Oid; use crate::collections::HashMap; use crate::identity; -use crate::identity::{IdError, ProjId, UserId}; +use crate::identity::{ProjId, ProjIdError, UserId}; pub static RAD_ID_GLOB: Lazy = Lazy::new(|| refspec::pattern!("refs/namespaces/*/refs/rad/id")); @@ -30,7 +30,7 @@ pub enum Error { #[error("git: {0}")] Git(#[from] git2::Error), #[error("id: {0}")] - ProjId(#[from] IdError), + ProjId(#[from] ProjIdError), #[error("i/o: {0}")] Io(#[from] io::Error), #[error("doc: {0}")] @@ -101,6 +101,11 @@ pub trait ReadStorage { pub trait WriteStorage { fn repository(&mut self) -> &mut git2::Repository; + fn namespace( + &mut self, + proj: &ProjId, + user: &UserId, + ) -> Result<&mut git2::Repository, git2::Error>; } impl ReadStorage for T @@ -125,6 +130,14 @@ where fn repository(&mut self) -> &mut git2::Repository { self.deref_mut().repository() } + + fn namespace( + &mut self, + proj: &ProjId, + user: &UserId, + ) -> Result<&mut git2::Repository, git2::Error> { + self.deref_mut().namespace(proj, user) + } } pub struct Storage { @@ -167,6 +180,19 @@ impl WriteStorage for Storage { fn repository(&mut self) -> &mut git2::Repository { &mut self.backend } + + fn namespace( + &mut self, + proj: &ProjId, + user: &UserId, + ) -> Result<&mut git2::Repository, git2::Error> { + let path = self.backend.path(); + + self.backend = git2::Repository::open_bare(path)?; + self.backend.set_namespace(&format!("{}/{}", proj, user))?; + + Ok(&mut self.backend) + } } impl Storage { diff --git a/node/src/test.rs b/node/src/test.rs index db9f6ec9..300ad876 100644 --- a/node/src/test.rs +++ b/node/src/test.rs @@ -1,5 +1,6 @@ pub(crate) mod arbitrary; pub(crate) mod assert; +pub(crate) mod fixtures; pub(crate) mod logger; pub(crate) mod peer; pub(crate) mod storage; diff --git a/node/src/test/arbitrary.rs b/node/src/test/arbitrary.rs index 9c1084ab..c4fa1e94 100644 --- a/node/src/test/arbitrary.rs +++ b/node/src/test/arbitrary.rs @@ -49,12 +49,8 @@ impl quickcheck::Arbitrary for ProjId { impl quickcheck::Arbitrary for hash::Digest { fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let mut bytes: [u8; 32] = [0; 32]; - - for byte in &mut bytes { - *byte = u8::arbitrary(g); - } - hash::Digest::from(bytes) + let bytes: Vec = quickcheck::Arbitrary::arbitrary(g); + hash::Digest::new(&bytes) } } diff --git a/node/src/test/storage.rs b/node/src/test/storage.rs index f6040501..815c1455 100644 --- a/node/src/test/storage.rs +++ b/node/src/test/storage.rs @@ -41,4 +41,12 @@ impl WriteStorage for MockStorage { fn repository(&mut self) -> &mut git2::Repository { todo!() } + + fn namespace( + &mut self, + _proj: &ProjId, + _user: &crate::identity::UserId, + ) -> Result<&mut git2::Repository, git2::Error> { + todo!() + } } diff --git a/node/src/test/tests.rs b/node/src/test/tests.rs index 23de5832..e2e07013 100644 --- a/node/src/test/tests.rs +++ b/node/src/test/tests.rs @@ -101,6 +101,22 @@ fn test_wrong_peer_magic() { // TODO } +#[test] +fn test_inventory_fetch() { + let mut alice = Peer::new("alice", [7, 7, 7, 7], MockStorage::empty()); + let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty()); + + alice.connect_to(&bob.addr()); + alice.receive( + &bob.addr(), + Message::Inventory { + seq: 1, + inv: vec![], + origin: None, + }, + ); +} + #[test] fn test_inventory_relay_bad_seq() { let mut alice = Peer::new("alice", [7, 7, 7, 7], MockStorage::empty());