lib

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

contract.rs (8074B)


      1 use crate::RADROOTS_RETICULUM_ENDPOINT_URI;
      2 use radroots_transport::target::EndpointUri;
      3 use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
      4 use radroots_transport::{Error as TransportError, Target, TransportId};
      5 
      6 pub const RETICULUM_V1_MAX_PAYLOAD_BYTES: usize = 64 * 1024;
      7 
      8 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
      9 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     10 pub enum ReticulumFragmentationModeV1 {
     11     Unsupported,
     12 }
     13 
     14 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     15 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     16 pub enum ReticulumDuplicateFragmentBehaviorV1 {
     17     Reject,
     18 }
     19 
     20 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     21 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     22 pub enum ReticulumFragmentIntegrityV1 {
     23     PayloadDigest,
     24 }
     25 
     26 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     27 #[derive(Clone, Debug, PartialEq, Eq)]
     28 pub struct ReticulumFragmentPolicyV1 {
     29     pub mode: ReticulumFragmentationModeV1,
     30     pub max_fragment_count: u16,
     31     pub max_reassembled_bytes: usize,
     32     pub duplicate_fragment_behavior: ReticulumDuplicateFragmentBehaviorV1,
     33     pub integrity_verification: ReticulumFragmentIntegrityV1,
     34 }
     35 
     36 impl ReticulumFragmentPolicyV1 {
     37     pub const fn unsupported() -> Self {
     38         Self {
     39             mode: ReticulumFragmentationModeV1::Unsupported,
     40             max_fragment_count: 1,
     41             max_reassembled_bytes: RETICULUM_V1_MAX_PAYLOAD_BYTES,
     42             duplicate_fragment_behavior: ReticulumDuplicateFragmentBehaviorV1::Reject,
     43             integrity_verification: ReticulumFragmentIntegrityV1::PayloadDigest,
     44         }
     45     }
     46 }
     47 
     48 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     49 #[derive(Clone, Debug, PartialEq, Eq)]
     50 pub struct ReticulumPayloadPolicyV1 {
     51     pub max_payload_bytes: usize,
     52     pub fragment_policy: ReticulumFragmentPolicyV1,
     53 }
     54 
     55 impl ReticulumPayloadPolicyV1 {
     56     pub const fn v1() -> Self {
     57         Self {
     58             max_payload_bytes: RETICULUM_V1_MAX_PAYLOAD_BYTES,
     59             fragment_policy: ReticulumFragmentPolicyV1::unsupported(),
     60         }
     61     }
     62 }
     63 
     64 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     65 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     66 pub enum ReticulumGatewaySemanticsV1 {
     67     NoGatewayForwarding,
     68 }
     69 
     70 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     71 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     72 pub enum ReticulumPrivacySemanticsV1 {
     73     CanonicalSignedEventBytesOnly,
     74 }
     75 
     76 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     77 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
     78 #[derive(Clone, Debug, PartialEq, Eq)]
     79 pub struct ReticulumRoutingMetadataV1 {
     80     pub scope: TargetScope,
     81     pub gateway: ReticulumGatewaySemanticsV1,
     82     pub privacy: ReticulumPrivacySemanticsV1,
     83 }
     84 
     85 impl ReticulumRoutingMetadataV1 {
     86     pub fn local() -> Self {
     87         Self {
     88             scope: TargetScope::parse(crate::RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope"),
     89             gateway: ReticulumGatewaySemanticsV1::NoGatewayForwarding,
     90             privacy: ReticulumPrivacySemanticsV1::CanonicalSignedEventBytesOnly,
     91         }
     92     }
     93 }
     94 
     95 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
     96 #[derive(Clone, Debug, PartialEq, Eq)]
     97 pub struct ReticulumDestinationV1 {
     98     uri: EndpointUri,
     99     routing: ReticulumRoutingMetadataV1,
    100     label: Option<TargetLabel>,
    101     fingerprint: TargetFingerprint,
    102 }
    103 
    104 impl ReticulumDestinationV1 {
    105     pub fn local() -> Self {
    106         Self::new(
    107             RADROOTS_RETICULUM_ENDPOINT_URI,
    108             ReticulumRoutingMetadataV1::local().scope,
    109             None,
    110         )
    111         .expect("local Reticulum destination")
    112     }
    113 
    114     pub fn new(
    115         uri: impl AsRef<str>,
    116         scope: TargetScope,
    117         label: Option<TargetLabel>,
    118     ) -> Result<Self, TransportError> {
    119         if uri.as_ref() != RADROOTS_RETICULUM_ENDPOINT_URI {
    120             return Err(TransportError::InvalidTargetUri);
    121         }
    122         let target = Target::new_with_metadata(
    123             TransportId::RETICULUM,
    124             uri.as_ref(),
    125             Some(scope),
    126             label.clone(),
    127         )?;
    128         Ok(Self {
    129             uri: target.uri().clone(),
    130             routing: ReticulumRoutingMetadataV1 {
    131                 scope: target
    132                     .scope()
    133                     .cloned()
    134                     .expect("Reticulum destination scope"),
    135                 gateway: ReticulumGatewaySemanticsV1::NoGatewayForwarding,
    136                 privacy: ReticulumPrivacySemanticsV1::CanonicalSignedEventBytesOnly,
    137             },
    138             label: target.label().cloned(),
    139             fingerprint: target.fingerprint().clone(),
    140         })
    141     }
    142 
    143     pub fn from_target(target: &Target) -> Result<Self, TransportError> {
    144         if target.kind() != &TransportId::RETICULUM
    145             || target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI
    146         {
    147             return Err(TransportError::InvalidTargetUri);
    148         }
    149         let Some(scope) = target.scope().cloned() else {
    150             return Err(TransportError::EmptyTargetScope);
    151         };
    152         let destination = Self::new(target.uri().as_str(), scope, target.label().cloned())?;
    153         if destination.fingerprint != *target.fingerprint() {
    154             return Err(TransportError::InvalidTargetFingerprint);
    155         }
    156         Ok(destination)
    157     }
    158 
    159     pub fn transport_target(&self) -> Result<Target, TransportError> {
    160         Target::new_with_metadata(
    161             TransportId::RETICULUM,
    162             self.uri.as_str(),
    163             Some(self.routing.scope.clone()),
    164             self.label.clone(),
    165         )
    166     }
    167 
    168     pub fn uri(&self) -> &EndpointUri {
    169         &self.uri
    170     }
    171 
    172     pub fn routing(&self) -> &ReticulumRoutingMetadataV1 {
    173         &self.routing
    174     }
    175 
    176     pub fn label(&self) -> Option<&TargetLabel> {
    177         self.label.as_ref()
    178     }
    179 
    180     pub fn fingerprint(&self) -> &TargetFingerprint {
    181         &self.fingerprint
    182     }
    183 }
    184 
    185 #[cfg(feature = "serde")]
    186 #[derive(serde::Deserialize)]
    187 #[serde(deny_unknown_fields)]
    188 struct ReticulumDestinationV1Wire {
    189     uri: EndpointUri,
    190     routing: ReticulumRoutingMetadataV1,
    191     label: Option<TargetLabel>,
    192     fingerprint: TargetFingerprint,
    193 }
    194 
    195 #[cfg(feature = "serde")]
    196 impl<'de> serde::Deserialize<'de> for ReticulumDestinationV1 {
    197     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    198     where
    199         D: serde::Deserializer<'de>,
    200     {
    201         let wire = ReticulumDestinationV1Wire::deserialize(deserializer)?;
    202         let destination = Self::new(
    203             wire.uri.as_str(),
    204             wire.routing.scope.clone(),
    205             wire.label.clone(),
    206         )
    207         .map_err(serde::de::Error::custom)?;
    208         if destination.routing != wire.routing
    209             || destination.label != wire.label
    210             || destination.fingerprint != wire.fingerprint
    211         {
    212             return Err(serde::de::Error::custom(
    213                 "Reticulum destination identity does not match its canonical fields",
    214             ));
    215         }
    216         Ok(destination)
    217     }
    218 }
    219 
    220 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    221 #[derive(Clone, Debug, PartialEq, Eq)]
    222 pub struct ReticulumCapabilityReportV1 {
    223     pub delivery_required: bool,
    224     pub fetch_required: bool,
    225     pub can_deliver: bool,
    226     pub can_fetch: bool,
    227     pub can_discover: bool,
    228     pub can_forward_gateway: bool,
    229     pub can_observe_receipts: bool,
    230     pub destination: ReticulumDestinationV1,
    231     pub payload_policy: ReticulumPayloadPolicyV1,
    232 }
    233 
    234 impl ReticulumCapabilityReportV1 {
    235     pub fn unavailable_local() -> Self {
    236         Self {
    237             delivery_required: true,
    238             fetch_required: false,
    239             can_deliver: false,
    240             can_fetch: false,
    241             can_discover: false,
    242             can_forward_gateway: false,
    243             can_observe_receipts: false,
    244             destination: ReticulumDestinationV1::local(),
    245             payload_policy: ReticulumPayloadPolicyV1::v1(),
    246         }
    247     }
    248 }