From f7ff4d8f4e9524b48da8c9c5ca21b8cda4a07327 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Tue, 3 Mar 2026 14:01:50 +0000 Subject: [PATCH] radicle/arbitrary: Move `impl Arbitrary` of refs Avoid leaking the types of the signed reference types by moving their `Arbitrary` implementations to a `refs::arbitrary` sub-module. Since `SignedRefsAt` require a correctly signed payload, a helper constructor is added: `signed_refs_at`. In turn, a function, `arbitrary::with_gen` is introduced so that this constructor can be easily called with a `Gen` value. --- crates/radicle/src/storage/refs.rs | 3 + crates/radicle/src/storage/refs/arbitrary.rs | 65 ++++++++++++++++ crates/radicle/src/test/arbitrary.rs | 80 ++------------------ 3 files changed, 76 insertions(+), 72 deletions(-) create mode 100644 crates/radicle/src/storage/refs/arbitrary.rs diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs index e5d2a1e0..f42c2fb2 100644 --- a/crates/radicle/src/storage/refs.rs +++ b/crates/radicle/src/storage/refs.rs @@ -1,3 +1,6 @@ +#[cfg(any(test, feature = "test"))] +pub mod arbitrary; + use std::collections::BTreeMap; use std::fmt::Debug; use std::io; diff --git a/crates/radicle/src/storage/refs/arbitrary.rs b/crates/radicle/src/storage/refs/arbitrary.rs new file mode 100644 index 00000000..7da31b25 --- /dev/null +++ b/crates/radicle/src/storage/refs/arbitrary.rs @@ -0,0 +1,65 @@ +#![allow(clippy::unwrap_used)] + +use qcheck::Arbitrary; + +use crate::node::device::Device; + +use super::*; + +pub fn signed_refs_at(g: &mut qcheck::Gen, root: Oid, signer: &Device) -> SignedRefsAt +where + S: crypto::signature::Signer, +{ + let mut refs = Refs::arbitrary(g); + refs.insert(IDENTITY_ROOT.to_ref_string(), root); + let signature = crypto::signature::Signer::sign(signer, &refs.canonical()); + let sigrefs = SignedRefs { + refs, + signature, + id: *signer.node_id(), + _verified: PhantomData, + }; + SignedRefsAt { + sigrefs, + at: Oid::from_sha1(Arbitrary::arbitrary(g)), + } +} + +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::from_sha1(Arbitrary::arbitrary(g)), + } + } +} diff --git a/crates/radicle/src/test/arbitrary.rs b/crates/radicle/src/test/arbitrary.rs index a9af6bf3..ac49b536 100644 --- a/crates/radicle/src/test/arbitrary.rs +++ b/crates/radicle/src/test/arbitrary.rs @@ -1,16 +1,14 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{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 crypto::PublicKey; 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::{ @@ -21,7 +19,6 @@ use crate::identity::{ 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}; @@ -109,13 +106,12 @@ pub fn gen(size: usize) -> T { 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) - } +pub fn with_gen(size: usize, f: F) -> T +where + F: FnOnce(&mut qcheck::Gen) -> T, +{ + let mut gen = qcheck::Gen::new(size); + f(&mut gen) } impl Arbitrary for Did { @@ -194,56 +190,6 @@ impl Arbitrary for DocAt { } } -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); @@ -260,16 +206,6 @@ impl Arbitrary for MockRepository { } } -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;