lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit d01962d8b548e08ecafd1d89935e1e16327ee367
parent edfb08189e8609d712a31f7aead8a96f2fb499f5
Author: triesap <tyson@radroots.org>
Date:   Sat, 18 Jul 2026 11:01:22 +0000

transport: close semantic contract coverage

- Normalize generic payload validation behind typed implementations.
- Remove unreachable satisfaction and target-validation branches.
- Exercise delivery, payload, status, Reticulum, and URI edges.
- Prove strict Clippy, tests, and the complete coverage gate.

Diffstat:
Mcrates/transport/src/delivery.rs | 5+----
Mcrates/transport/src/lib.rs | 12++++++++++++
Mcrates/transport/src/payload.rs | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcrates/transport/src/target.rs | 7++-----
Mcrates/transport/tests/transport.rs | 382++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 450 insertions(+), 23 deletions(-)

diff --git a/crates/transport/src/delivery.rs b/crates/transport/src/delivery.rs @@ -187,10 +187,7 @@ impl RadrootsTransportSatisfactionPolicy { &self, total_targets: usize, ) -> Result<usize, RadrootsTransportError> { - if matches!(self, Self::NoWait) { - return Ok(0); - } - if total_targets == 0 { + if total_targets == 0 && !matches!(self, Self::NoWait) { return Err(RadrootsTransportError::InvalidSatisfactionPolicy); } match self { diff --git a/crates/transport/src/lib.rs b/crates/transport/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![forbid(unsafe_code)] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] extern crate alloc; @@ -46,3 +47,14 @@ pub use transport::{ RadrootsTransport, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest, RadrootsTransportFuture, }; + +#[cfg(test)] +extern crate self as radroots_transport; + +#[cfg(test)] +extern crate std; + +#[cfg(test)] +#[cfg_attr(coverage_nightly, coverage(off))] +#[path = "../tests/transport.rs"] +mod tests; diff --git a/crates/transport/src/payload.rs b/crates/transport/src/payload.rs @@ -28,8 +28,12 @@ impl RadrootsTransportPayload { event_id: impl AsRef<str>, raw_json: impl AsRef<str>, ) -> Result<Self, RadrootsTransportError> { - let event_id = validate_hex_id(event_id.as_ref())?; - let raw_json = validate_raw_json(raw_json.as_ref())?; + Self::signed_event_json(event_id.as_ref(), raw_json.as_ref()) + } + + fn signed_event_json(event_id: &str, raw_json: &str) -> Result<Self, RadrootsTransportError> { + let event_id = validate_hex_id(event_id)?; + let raw_json = validate_raw_json(raw_json)?; let digest = sha256_hex(raw_json.as_bytes()); Ok(Self::SignedEventJson { event_id, @@ -43,8 +47,16 @@ impl RadrootsTransportPayload { raw_json: impl AsRef<str>, digest: impl AsRef<str>, ) -> Result<Self, RadrootsTransportError> { - let payload = Self::unchecked_signed_event_json(event_id, raw_json)?; - validate_supplied_digest(payload.digest(), digest.as_ref())?; + Self::signed_event_json_with_digest(event_id.as_ref(), raw_json.as_ref(), digest.as_ref()) + } + + fn signed_event_json_with_digest( + event_id: &str, + raw_json: &str, + digest: &str, + ) -> Result<Self, RadrootsTransportError> { + let payload = Self::signed_event_json(event_id, raw_json)?; + validate_supplied_digest(payload.digest(), digest)?; Ok(payload) } @@ -52,8 +64,15 @@ impl RadrootsTransportPayload { message_id: impl AsRef<str>, bytes: impl AsRef<[u8]>, ) -> Result<Self, RadrootsTransportError> { - let message_id = validate_token_id(message_id.as_ref())?; - let bytes = validate_bytes(bytes.as_ref())?; + Self::validated_mesh_frame_cbor(message_id.as_ref(), bytes.as_ref()) + } + + fn validated_mesh_frame_cbor( + message_id: &str, + bytes: &[u8], + ) -> Result<Self, RadrootsTransportError> { + let message_id = validate_token_id(message_id)?; + let bytes = validate_bytes(bytes)?; let digest = sha256_hex(bytes.as_slice()); Ok(Self::MeshFrameCbor { message_id, @@ -67,8 +86,20 @@ impl RadrootsTransportPayload { bytes: impl AsRef<[u8]>, digest: impl AsRef<str>, ) -> Result<Self, RadrootsTransportError> { - let payload = Self::mesh_frame_cbor(message_id, bytes)?; - validate_supplied_digest(payload.digest(), digest.as_ref())?; + Self::validated_mesh_frame_cbor_with_digest( + message_id.as_ref(), + bytes.as_ref(), + digest.as_ref(), + ) + } + + fn validated_mesh_frame_cbor_with_digest( + message_id: &str, + bytes: &[u8], + digest: &str, + ) -> Result<Self, RadrootsTransportError> { + let payload = Self::validated_mesh_frame_cbor(message_id, bytes)?; + validate_supplied_digest(payload.digest(), digest)?; Ok(payload) } @@ -76,8 +107,12 @@ impl RadrootsTransportPayload { label: impl AsRef<str>, bytes: impl AsRef<[u8]>, ) -> Result<Self, RadrootsTransportError> { - let label = validate_label(label.as_ref())?; - let bytes = validate_bytes(bytes.as_ref())?; + Self::validated_opaque_bytes(label.as_ref(), bytes.as_ref()) + } + + fn validated_opaque_bytes(label: &str, bytes: &[u8]) -> Result<Self, RadrootsTransportError> { + let label = validate_label(label)?; + let bytes = validate_bytes(bytes)?; let digest = sha256_hex(bytes.as_slice()); Ok(Self::OpaqueBytes { label, @@ -91,8 +126,16 @@ impl RadrootsTransportPayload { bytes: impl AsRef<[u8]>, digest: impl AsRef<str>, ) -> Result<Self, RadrootsTransportError> { - let payload = Self::opaque_bytes(label, bytes)?; - validate_supplied_digest(payload.digest(), digest.as_ref())?; + Self::validated_opaque_bytes_with_digest(label.as_ref(), bytes.as_ref(), digest.as_ref()) + } + + fn validated_opaque_bytes_with_digest( + label: &str, + bytes: &[u8], + digest: &str, + ) -> Result<Self, RadrootsTransportError> { + let payload = Self::validated_opaque_bytes(label, bytes)?; + validate_supplied_digest(payload.digest(), digest)?; Ok(payload) } diff --git a/crates/transport/src/target.rs b/crates/transport/src/target.rs @@ -212,10 +212,7 @@ impl RadrootsTransportTarget { RadrootsTransportKind::Nostr => RadrootsTransportTargetUri::parse_nostr_relay(raw_uri)?, _ => RadrootsTransportTargetUri::parse(raw_uri)?, }; - if kind == RadrootsTransportKind::Reticulum - && (raw_uri != RADROOTS_RETICULUM_ENDPOINT_URI - || uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI) - { + if kind == RadrootsTransportKind::Reticulum && raw_uri != RADROOTS_RETICULUM_ENDPOINT_URI { return Err(RadrootsTransportError::InvalidTargetUri); } let scope = scope.or_else(|| default_scope_for_kind(&kind)); @@ -381,7 +378,7 @@ fn canonicalize_nostr_relay_authority( .next() .map(parse_nostr_relay_port_with_prefix) .transpose()?; - if host.is_empty() || !is_valid_nostr_relay_host(host) { + if !is_valid_nostr_relay_host(host) { return Err(RadrootsTransportError::InvalidTargetUri); } (host.to_ascii_lowercase(), port) diff --git a/crates/transport/tests/transport.rs b/crates/transport/tests/transport.rs @@ -1,6 +1,7 @@ use radroots_transport::{ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID, RadrootsTransport, - RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt, + RadrootsTransportCapabilities, RadrootsTransportCapabilityAvailability, + RadrootsTransportCapabilityMaturity, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest, RadrootsTransportFuture, RadrootsTransportImplementationState, RadrootsTransportKind, @@ -9,11 +10,16 @@ use radroots_transport::{ RadrootsTransportSatisfactionPolicy, RadrootsTransportStatus, RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel, RadrootsTransportTargetReceipt, RadrootsTransportTargetSet, RadrootsTransportTargetUri, - ReticulumCapabilityReportV1, ReticulumDuplicateFragmentBehaviorV1, + ReticulumCapabilityReportV1, ReticulumDestinationV1, ReticulumDuplicateFragmentBehaviorV1, ReticulumFragmentIntegrityV1, ReticulumFragmentationModeV1, ReticulumGatewaySemanticsV1, ReticulumPrivacySemanticsV1, }; use serde_json::Value; +use std::borrow::ToOwned; +use std::boxed::Box; +use std::string::{String, ToString}; +use std::vec; +use std::vec::Vec; fn opaque_payload() -> RadrootsTransportPayload { RadrootsTransportPayload::opaque_bytes("transport-test-payload", b"transport payload") @@ -1239,3 +1245,375 @@ fn neutral_transport_trait_covers_status_delivery_and_fetch() { RadrootsTransportOutcomeKind::Seen ); } + +#[test] +fn delivery_contract_covers_every_policy_and_receipt_path() { + let one = RadrootsTransportTarget::nostr_relay("wss://one.example").expect("one"); + let two = RadrootsTransportTarget::nostr_relay("wss://two.example").expect("two"); + let request = RadrootsTransportDeliveryRequest::new( + "delivery", + opaque_payload(), + RadrootsTransportTargetSet::new(vec![one.clone(), two.clone()]).expect("target set"), + RadrootsTransportSatisfactionPolicy::all_accepted(), + ) + .with_now_ms(42); + assert_eq!(request.now_ms, 42); + + for (policy, class) in [ + ( + RadrootsTransportSatisfactionPolicy::any_delivered(), + RadrootsTransportSatisfactionClass::Delivered, + ), + ( + RadrootsTransportSatisfactionPolicy::quorum_durable_or_observed(2), + RadrootsTransportSatisfactionClass::DurableOrObserved, + ), + ] { + assert_eq!(policy.target_satisfaction_class(), Some(class)); + } + for policy in [ + RadrootsTransportSatisfactionPolicy::no_wait(), + RadrootsTransportSatisfactionPolicy::any_accepted(), + RadrootsTransportSatisfactionPolicy::all_accepted(), + RadrootsTransportSatisfactionPolicy::quorum_accepted(1), + ] { + assert!(policy.required_target_fingerprints().is_none()); + } + + let required = RadrootsTransportSatisfactionPolicy::required_targets( + RadrootsTransportSatisfactionClass::Accepted, + vec![one.fingerprint.clone(), two.fingerprint.clone()], + ) + .expect("required policy"); + assert_eq!( + required + .required_target_count(1) + .expect_err("required set exceeds total"), + RadrootsTransportError::InvalidSatisfactionPolicy + ); + + let accepted = RadrootsTransportTargetReceipt::new( + one.clone(), + RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Accepted), + ); + let rejected = RadrootsTransportTargetReceipt::new( + two.clone(), + RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Rejected), + ); + let receipt = RadrootsTransportDeliveryReceipt { + request_id: "delivery".to_owned(), + target_receipts: vec![accepted, rejected], + }; + assert!( + receipt + .is_satisfied_by(&RadrootsTransportSatisfactionPolicy::no_wait()) + .expect("no wait") + ); + assert!( + receipt + .is_satisfied_by(&RadrootsTransportSatisfactionPolicy::any_accepted()) + .expect("any") + ); + assert!( + !receipt + .is_satisfied_by(&RadrootsTransportSatisfactionPolicy::all_accepted()) + .expect("all") + ); + assert!( + receipt + .is_satisfied_by(&RadrootsTransportSatisfactionPolicy::quorum_accepted(1)) + .expect("quorum") + ); + assert!(!receipt.is_satisfied_by(&required).expect("required")); + + let invalid_empty = RadrootsTransportSatisfactionPolicy::RequiredTargets { + class: RadrootsTransportSatisfactionClass::Accepted, + targets: Vec::new(), + }; + assert_eq!( + invalid_empty + .required_target_count(2) + .expect_err("empty required set"), + RadrootsTransportError::EmptyRequiredTargetSet + ); + assert_eq!( + receipt + .is_satisfied_by(&invalid_empty) + .expect_err("empty required receipt policy"), + RadrootsTransportError::EmptyRequiredTargetSet + ); + + let duplicate = RadrootsTransportSatisfactionPolicy::RequiredTargets { + class: RadrootsTransportSatisfactionClass::Accepted, + targets: vec![one.fingerprint.clone(), one.fingerprint], + }; + assert_eq!( + duplicate + .required_target_count(2) + .expect_err("duplicate required set"), + RadrootsTransportError::DuplicateRequiredTargetFingerprint + ); +} + +#[test] +fn payload_contract_covers_all_validation_boundaries() { + let signed = RadrootsTransportPayload::unchecked_signed_event_json("a".repeat(64), "{}") + .expect("signed"); + let mesh = RadrootsTransportPayload::mesh_frame_cbor("mesh", [1]).expect("mesh"); + let opaque = RadrootsTransportPayload::opaque_bytes(" label ", [2]).expect("opaque"); + assert_eq!(signed.payload_kind(), "signed_event_json"); + assert_eq!(mesh.payload_kind(), "mesh_frame_cbor"); + assert_eq!(opaque.payload_kind(), "opaque_bytes"); + + for invalid_id in ["a".repeat(63), "g".repeat(64)] { + assert_eq!( + RadrootsTransportPayload::unchecked_signed_event_json(invalid_id, "{}") + .expect_err("invalid event id"), + RadrootsTransportError::InvalidPayloadId + ); + } + assert_eq!( + RadrootsTransportPayload::mesh_frame_cbor("", [1]).expect_err("empty message id"), + RadrootsTransportError::EmptyPayloadId + ); + for invalid_id in [" mesh", "mesh/one", "mesh\n"] { + assert_eq!( + RadrootsTransportPayload::mesh_frame_cbor(invalid_id, [1]) + .expect_err("invalid token id"), + RadrootsTransportError::InvalidPayloadId + ); + } + assert_eq!( + RadrootsTransportPayload::opaque_bytes(" ", [1]).expect_err("empty label"), + RadrootsTransportError::EmptyPayloadLabel + ); + assert_eq!( + RadrootsTransportPayload::opaque_bytes("label", []).expect_err("empty opaque bytes"), + RadrootsTransportError::EmptyPayloadBytes + ); + assert_eq!( + RadrootsTransportPayload::unchecked_signed_event_json("a".repeat(64), "") + .expect_err("empty raw json"), + RadrootsTransportError::EmptyPayloadBytes + ); + for invalid_json in [" {}", "{} ", "{\n}", "[]", "{", "}"] { + assert_eq!( + RadrootsTransportPayload::unchecked_signed_event_json("a".repeat(64), invalid_json) + .expect_err("invalid raw json"), + RadrootsTransportError::InvalidPayloadBytes + ); + } + for invalid_digest in ["f".repeat(63), "g".repeat(64)] { + assert_eq!( + RadrootsTransportPayload::opaque_bytes_with_digest("label", [1], invalid_digest) + .expect_err("invalid digest"), + RadrootsTransportError::InvalidPayloadDigest + ); + } + + assert_eq!( + RadrootsTransportPayload::unchecked_signed_event_json_with_digest( + "bad", + "{}", + "f".repeat(64), + ) + .expect_err("invalid signed event before digest"), + RadrootsTransportError::InvalidPayloadId + ); + assert_eq!( + RadrootsTransportPayload::unchecked_signed_event_json_with_digest( + "a".repeat(64), + "{}", + "bad", + ) + .expect_err("invalid signed digest"), + RadrootsTransportError::InvalidPayloadDigest + ); + assert_eq!( + RadrootsTransportPayload::mesh_frame_cbor_with_digest("", [1], "f".repeat(64)) + .expect_err("invalid mesh before digest"), + RadrootsTransportError::EmptyPayloadId + ); + assert_eq!( + RadrootsTransportPayload::mesh_frame_cbor_with_digest("mesh", [1], "bad") + .expect_err("invalid mesh digest"), + RadrootsTransportError::InvalidPayloadDigest + ); + assert_eq!( + RadrootsTransportPayload::opaque_bytes_with_digest("", [1], "f".repeat(64)) + .expect_err("invalid opaque payload before digest"), + RadrootsTransportError::EmptyPayloadLabel + ); +} + +#[test] +fn status_contract_covers_builders_and_availability_defaults() { + assert_eq!( + RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Accepted) + .with_code("accepted") + .with_message("accepted by transport") + .code + .as_deref(), + Some("accepted") + ); + + assert!(!RadrootsTransportCapabilities::none().deliver); + assert!(RadrootsTransportCapabilities::fetch_only().fetch); + assert_eq!( + RadrootsTransportCapabilities::reticulum_unavailable(), + RadrootsTransportCapabilities::none() + ); + let capabilities = RadrootsTransportCapabilities::deliver_and_fetch() + .with_discovery(true) + .with_gateway_forwarding(true) + .with_receipt_observation(true); + assert!(capabilities.deliver); + assert!(capabilities.fetch); + assert!(capabilities.discovery); + assert!(capabilities.gateway_forwarding); + assert!(capabilities.receipt_observation); + + let unavailable = RadrootsTransportStatus::new( + RadrootsTransportKind::Reticulum, + true, + RadrootsTransportImplementationState::Mock, + false, + "unavailable", + ) + .with_capabilities(capabilities.clone()) + .with_maturity(RadrootsTransportCapabilityMaturity::Preview) + .with_availability(RadrootsTransportCapabilityAvailability::Degraded) + .with_profile_id("reticulum.local") + .with_endpoint_uri(RADROOTS_RETICULUM_ENDPOINT_URI); + assert_eq!( + unavailable.availability, + RadrootsTransportCapabilityAvailability::Degraded + ); + assert_eq!( + unavailable.maturity, + RadrootsTransportCapabilityMaturity::Preview + ); + assert_eq!(unavailable.capabilities, capabilities); + assert!(!unavailable.usable_for_delivery); + + assert!(!RadrootsTransportDeliveryTargetStatus::Accepted.is_ready_for_attempt()); + assert!(!RadrootsTransportDeliveryTargetStatus::Accepted.is_retryable_failure()); + assert!(RadrootsTransportDeliveryTargetStatus::SkippedPolicyDenied.is_terminal_failure()); + assert!(!RadrootsTransportDeliveryTargetStatus::Accepted.is_terminal_failure()); + assert!(!RadrootsTransportDeliveryTargetStatus::Accepted.is_deferred_until_implemented()); +} + +#[test] +#[cfg(feature = "serde")] +fn transport_kind_deserializer_rejects_non_string_values() { + assert!(serde_json::from_str::<RadrootsTransportKind>("1").is_err()); + assert!(serde_json::from_str::<RadrootsTransportKind>("\"NOSTR\"").is_err()); +} + +#[test] +fn reticulum_destination_rejects_wrong_kind_uri_and_missing_scope() { + let local = RadrootsTransportTarget::local("local:memory").expect("local target"); + assert_eq!( + ReticulumDestinationV1::from_target(&local).expect_err("wrong kind"), + RadrootsTransportError::InvalidTargetUri + ); + + let mut wrong_uri = RadrootsTransportTarget::reticulum().expect("Reticulum target"); + wrong_uri.uri = RadrootsTransportTargetUri::parse("reticulum:other").expect("generic URI"); + assert_eq!( + ReticulumDestinationV1::from_target(&wrong_uri).expect_err("wrong URI"), + RadrootsTransportError::InvalidTargetUri + ); + + let mut missing_scope = RadrootsTransportTarget::reticulum().expect("Reticulum target"); + missing_scope.scope = None; + assert_eq!( + ReticulumDestinationV1::from_target(&missing_scope).expect_err("missing scope"), + RadrootsTransportError::EmptyTargetScope + ); +} + +#[test] +fn target_contract_covers_parser_and_authority_boundaries() { + let scope = RadrootsTransportMeshScopeId::parse("farm_1.alpha-beta").expect("scope"); + assert_eq!(scope.as_str(), "farm_1.alpha-beta"); + assert_eq!(scope.to_string(), "farm_1.alpha-beta"); + for invalid_scope in [" scope", "scope ", "scope/path", "scope\n"] { + assert_eq!( + RadrootsTransportMeshScopeId::parse(invalid_scope).expect_err("invalid scope"), + RadrootsTransportError::InvalidTargetScope + ); + } + + let label = RadrootsTransportTargetLabel::parse(" Relay One ").expect("label"); + assert_eq!(label.as_str(), "Relay One"); + assert_eq!(label.to_string(), "Relay One"); + assert_eq!( + RadrootsTransportTargetLabel::parse("\u{7f}").expect_err("control label"), + RadrootsTransportError::InvalidTargetLabel + ); + + for invalid in [ + "", + " wss://relay.example", + "wss://relay example", + "wss://relay\u{7f}.example", + "relay.example", + "ftp://relay.example", + "wss://[::1", + "wss://[]", + "wss://[::1]suffix", + "wss://[[::1]]", + "wss://relay[.example", + "wss://.relay.example", + "wss://relay..example", + "wss://relay.example.", + "wss://relay_example", + "wss://relay.example:", + "wss://[::1]:", + "wss://[::1]:bad", + "wss://[::1]:42949672960", + "ws://[2001:db8::1]", + ] { + assert_eq!( + RadrootsTransportTarget::nostr_relay(invalid).expect_err("invalid relay URI"), + if invalid.is_empty() { + RadrootsTransportError::EmptyTargetUri + } else { + RadrootsTransportError::InvalidTargetUri + }, + "{invalid}" + ); + } + + for (raw, canonical) in [ + ("WSS://[2001:DB8::1]", "wss://[2001:db8::1]"), + ("wss://relay.example:443/", "wss://relay.example:443"), + ("ws://127.0.0.1", "ws://127.0.0.1"), + ] { + assert_eq!( + RadrootsTransportTarget::nostr_relay(raw) + .expect("relay URI") + .uri + .as_str(), + canonical + ); + } +} + +#[test] +fn every_transport_error_has_a_stable_display_message() { + let remaining = [ + RadrootsTransportError::EmptyPayloadId, + RadrootsTransportError::InvalidPayloadId, + RadrootsTransportError::EmptyPayloadLabel, + RadrootsTransportError::InvalidPayloadLabel, + RadrootsTransportError::EmptyPayloadBytes, + RadrootsTransportError::InvalidPayloadBytes, + RadrootsTransportError::InvalidPayloadDigest, + RadrootsTransportError::PayloadDigestMismatch, + ]; + for error in remaining { + assert!(!error.to_string().is_empty()); + } +}