use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::Hash; use std::ops::RangeBounds; use std::str::FromStr; use std::{iter, net}; use crypto::test::signer::MockSigner; use crypto::{PublicKey, Unverified}; use cyphernet::addr::tor::OnionAddrV3; use cyphernet::EcPk; use qcheck::Arbitrary; use crate::collections::RandomMap; use crate::identity::doc::Visibility; use crate::identity::project::ProjectName; use crate::identity::{ doc::{Doc, DocAt, RawDoc, RepoId}, project::Project, Did, }; use crate::node::address::{AddressType, Source}; use crate::node::{Address, Alias, KnownAddress, Timestamp, UserAgent}; use crate::storage; use crate::storage::refs::{Refs, RefsAt, SignedRefs}; use crate::test::storage::{MockRepository, MockStorage}; use crate::{cob, git}; pub fn oid() -> storage::Oid { let oid_bytes: [u8; 20] = gen(1); storage::Oid::from_sha1(oid_bytes) } pub fn entry_id() -> cob::EntryId { self::oid() } pub fn refstring(len: usize) -> git::fmt::RefString { let mut buf = Vec::::new(); for _ in 0..len { buf.push(fastrand::u8(0x61..0x7a)); } std::str::from_utf8(&buf) .unwrap() .to_string() .try_into() .unwrap() } pub fn set(range: impl RangeBounds) -> HashSet { let size = fastrand::usize(range); let mut set = HashSet::with_capacity(size); let mut g = qcheck::Gen::new(size); while set.len() < size { set.insert(T::arbitrary(&mut g)); } set } pub fn vec(size: usize) -> Vec { let mut vec = Vec::with_capacity(size); let mut g = qcheck::Gen::new(size); for _ in 0..vec.capacity() { vec.push(T::arbitrary(&mut g)); } vec } pub fn nonempty_storage(size: usize) -> MockStorage { let mut storage = gen::(size); for _ in 0..size { let doc = gen::(1); let id = RepoId::from(doc.blob); storage.repos.insert( id, MockRepository { id, doc, remotes: HashMap::new(), }, ); } storage } /// Generate a `String` of length `size`, only containing alphanumeric /// characters, i.e. [A-Za-z0-9] pub fn alphanumeric(size: usize) -> String { let mut s = String::with_capacity(size); for _ in 0..size { let choice = gen::(size).clamp(0, 3); let c = match choice { // Generate A-Z 0 => gen::(size).clamp(0x41, 0x5A), // Generate a-z 1 => gen::(size).clamp(0x61, 0x7A), // Generate 0-9 _ => gen::(size).clamp(0x30, 0x39), }; s.push(char::from(c)); } s } pub fn gen(size: usize) -> T { let mut gen = qcheck::Gen::new(size); T::arbitrary(&mut gen) } impl Arbitrary for storage::Remotes { fn arbitrary(g: &mut qcheck::Gen) -> Self { let remotes: RandomMap> = Arbitrary::arbitrary(g); storage::Remotes::new(remotes) } } impl Arbitrary for Did { fn arbitrary(g: &mut qcheck::Gen) -> Self { Self::from(PublicKey::arbitrary(g)) } } impl Arbitrary for Project { fn arbitrary(g: &mut qcheck::Gen) -> Self { let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let length = rng.usize(1..16); let name: String = iter::repeat_with(|| rng.alphanumeric()) .take(length) .collect(); let name = ProjectName::from_str(&name).unwrap(); let description = iter::repeat_with(|| rng.alphanumeric()) .take(length * 2) .collect(); let default_branch: git::fmt::RefString = iter::repeat_with(|| rng.alphanumeric()) .take(length) .collect::() .try_into() .unwrap(); Project::new(name, description, default_branch).unwrap() } } impl Arbitrary for Visibility { fn arbitrary(g: &mut qcheck::Gen) -> Self { if bool::arbitrary(g) { Visibility::Public } else { Visibility::Private { allow: BTreeSet::arbitrary(g), } } } } impl Arbitrary for RawDoc { fn arbitrary(g: &mut qcheck::Gen) -> Self { let proj = Project::arbitrary(g); let delegate = Did::arbitrary(g); let visibility = Visibility::arbitrary(g); Self::new(proj, vec![delegate], 1, visibility) } } impl Arbitrary for Doc { fn arbitrary(g: &mut qcheck::Gen) -> Self { let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let project = Project::arbitrary(g); let delegates = iter::repeat_with(|| Did::arbitrary(g)) .take(rng.usize(1..6)) .collect::>(); let threshold = delegates.len() / 2 + 1; let visibility = Visibility::arbitrary(g); let doc = RawDoc::new(project, delegates, threshold, visibility); doc.verified().unwrap() } } impl Arbitrary for DocAt { fn arbitrary(g: &mut qcheck::Gen) -> Self { let doc = Doc::arbitrary(g); DocAt { commit: self::oid(), blob: self::oid(), doc, } } } impl Arbitrary for SignedRefs { fn arbitrary(g: &mut qcheck::Gen) -> Self { let bytes: [u8; 64] = Arbitrary::arbitrary(g); let signature = crypto::Signature::from(bytes); let author = PublicKey::arbitrary(g); let refs = Refs::arbitrary(g); Self::new(refs, author, signature) } } impl Arbitrary for Refs { fn arbitrary(g: &mut qcheck::Gen) -> Self { let mut refs: BTreeMap = BTreeMap::new(); let mut bytes: [u8; 20] = [0; 20]; let names = &[ "heads/master", "heads/feature/1", "heads/feature/2", "heads/feature/3", "rad/id", "tags/v1.0", "tags/v2.0", "notes/1", ]; for _ in 0..g.size().min(names.len()) { if let Some(name) = g.choose(names) { for byte in &mut bytes { *byte = u8::arbitrary(g); } let oid = storage::Oid::from_sha1(bytes); let name = git::fmt::RefString::try_from(*name).unwrap(); refs.insert(name, oid); } } Self::from(refs) } } impl Arbitrary for RefsAt { fn arbitrary(g: &mut qcheck::Gen) -> Self { Self { remote: PublicKey::arbitrary(g), at: oid(), } } } impl Arbitrary for MockStorage { fn arbitrary(g: &mut qcheck::Gen) -> Self { let inventory = Arbitrary::arbitrary(g); MockStorage::new(inventory) } } impl Arbitrary for MockRepository { fn arbitrary(g: &mut qcheck::Gen) -> Self { let rid = RepoId::arbitrary(g); let doc = Doc::arbitrary(g); Self::new(rid, doc) } } impl Arbitrary for storage::Remote { fn arbitrary(g: &mut qcheck::Gen) -> Self { let refs = Refs::arbitrary(g); let signer = MockSigner::arbitrary(g); let signed = refs.signed(&signer.into()).unwrap(); storage::Remote::::new(signed) } } impl Arbitrary for AddressType { fn arbitrary(g: &mut qcheck::Gen) -> Self { let t = *g.choose(&[1, 2, 3, 4]).unwrap() as u8; AddressType::try_from(t).unwrap() } } impl Arbitrary for Address { fn arbitrary(g: &mut qcheck::Gen) -> Self { let host = match AddressType::arbitrary(g) { AddressType::Ipv4 => cyphernet::addr::HostName::Ip(net::IpAddr::V4( net::Ipv4Addr::from(u32::arbitrary(g)), )), AddressType::Ipv6 => { let octets: [u8; 16] = Arbitrary::arbitrary(g); cyphernet::addr::HostName::Ip(net::IpAddr::V6(net::Ipv6Addr::from(octets))) } AddressType::Dns => cyphernet::addr::HostName::Dns( g.choose(&["iris.radicle.example.com", "rosa.radicle.example.com"]) .unwrap() .to_string(), ), AddressType::Onion => { let pk = PublicKey::arbitrary(g); let addr = OnionAddrV3::from( cyphernet::ed25519::PublicKey::from_pk_compressed(**pk).unwrap(), ); cyphernet::addr::HostName::Tor(addr) } }; Address::from(cyphernet::addr::NetAddr { host, port: u16::arbitrary(g), }) } } impl Arbitrary for KnownAddress { fn arbitrary(g: &mut qcheck::Gen) -> Self { KnownAddress::new(Address::arbitrary(g), Source::Peer) } } impl Arbitrary for Alias { fn arbitrary(g: &mut qcheck::Gen) -> Self { let s = g .choose(&["cloudhead", "alice", "bob", "john-lu", "f0_"]) .unwrap(); Alias::from_str(s).unwrap() } } impl Arbitrary for Timestamp { fn arbitrary(g: &mut qcheck::Gen) -> Self { Self::try_from(u64::arbitrary(g).min(*Self::MAX)).unwrap() } } impl Arbitrary for UserAgent { fn arbitrary(g: &mut qcheck::Gen) -> Self { UserAgent::from_str( format!("/radicle:1.{}.{}/", u8::arbitrary(g), u8::arbitrary(g)).as_str(), ) .unwrap() } }