Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Fix encoding of types, add fixtures
Alexis Sellier committed 3 years ago
commit 32591e6aa45a717bb5e44fe4406b9210d5426884
parent 1ff8f625ff4922894a72d7d62290e125354b00d0
7 files changed +127 -46
modified 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<Self, ParseError> {
-
        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<Self, DecodeError> {
+
        let (_, bytes) = multibase::decode(s)?;
+
        let array = bytes
+
            .try_into()
+
            .map_err(|v: Vec<u8>| DecodeError::InvalidLength(v.len()))?;
+

+
        Ok(Self(array))
+
    }
+
}
+

+
impl FromStr for Digest {
+
    type Err = DecodeError;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        Self::decode(s)
    }
}

@@ -77,3 +79,17 @@ impl From<GenericArray<u8, <Sha256 as OutputSizeUser>::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);
+
    }
+
}
modified 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<ProjId, IdError> {
+
    pub(crate) fn from_ref(s: &str) -> Result<ProjId, ProjIdError> {
        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?
+
            let id = Self::from_str(s)?;
+
            return Ok(id);
+
        }
+
        Err(ProjIdError::InvalidRef(s.to_owned()))
+
    }
+
}

-
            assert_eq!(bytes, array.len());
+
impl FromStr for ProjId {
+
    type Err = hash::DecodeError;

-
            return Ok(Self(hash::Digest::from(array)));
-
        }
-
        Err(IdError::InvalidRef(s.to_owned()))
+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        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<Self, Self::Err> {
-
        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<u8>| 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);
+
    }
}
modified 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<refspec::PatternString> =
    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<T, S> 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 {
modified 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;
modified 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<u8> = quickcheck::Arbitrary::arbitrary(g);
+
        hash::Digest::new(&bytes)
    }
}

modified 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!()
+
    }
}
modified node/src/test/tests.rs
@@ -102,6 +102,22 @@ fn test_wrong_peer_magic() {
}

#[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());
    let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty());