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 }