target_contract.rs (15761B)
1 use core::str::FromStr; 2 3 use radroots_transport::{ 4 Error, TARGET_SET_MAX_ITEMS, Target, TargetNetworkPolicy, TargetSet, TransportId, 5 endpoint::{ 6 ENDPOINT_URI_MAX_BYTES, EndpointUri, TARGET_LABEL_MAX_BYTES, TARGET_SCOPE_MAX_BYTES, 7 TargetLabel, TargetScope, 8 }, 9 target::TargetFingerprint, 10 }; 11 use serde_json::Value; 12 13 #[test] 14 fn endpoint_scope_and_label_values_are_canonical_and_bounded() { 15 let endpoint = EndpointUri::from_str("MESH://Node.Example/path").expect("endpoint"); 16 assert_eq!(endpoint.as_ref(), "mesh://node.example/path"); 17 assert_eq!(EndpointUri::try_from(endpoint.as_str()).unwrap(), endpoint); 18 assert_eq!( 19 EndpointUri::parse("a".repeat(ENDPOINT_URI_MAX_BYTES + 1)).unwrap_err(), 20 Error::InvalidTargetUri 21 ); 22 23 let scope = TargetScope::from_str("farm_1.alpha-beta").expect("scope"); 24 assert_eq!(scope.as_ref(), "farm_1.alpha-beta"); 25 assert_eq!(TargetScope::try_from(scope.as_str()).unwrap(), scope); 26 assert_eq!( 27 TargetScope::parse("a".repeat(TARGET_SCOPE_MAX_BYTES + 1)).unwrap_err(), 28 Error::InvalidTargetScope 29 ); 30 31 let label = TargetLabel::from_str(" Relay One ").expect("label"); 32 assert_eq!(label.as_ref(), "Relay One"); 33 assert_eq!(TargetLabel::try_from(label.as_str()).unwrap(), label); 34 assert_eq!( 35 TargetLabel::parse("a".repeat(TARGET_LABEL_MAX_BYTES + 1)).unwrap_err(), 36 Error::InvalidTargetLabel 37 ); 38 } 39 40 #[test] 41 fn fingerprints_and_target_sets_are_deterministic_unique_and_bounded() { 42 let target = Target::new(TransportId::LOCAL, "local:node-0").expect("target"); 43 let fingerprint = 44 TargetFingerprint::from_str(target.fingerprint().as_str()).expect("target fingerprint"); 45 assert_eq!(&fingerprint, target.fingerprint()); 46 assert_eq!( 47 TargetFingerprint::try_from(fingerprint.as_str()).unwrap(), 48 fingerprint 49 ); 50 51 let targets = (0..TARGET_SET_MAX_ITEMS) 52 .map(|index| { 53 Target::new(TransportId::LOCAL, format!("local:node-{index}")).expect("bounded target") 54 }) 55 .collect(); 56 let target_set = TargetSet::new(targets).expect("maximum target set"); 57 assert_eq!(target_set.len(), TARGET_SET_MAX_ITEMS); 58 59 let oversized = (0..=TARGET_SET_MAX_ITEMS) 60 .map(|index| { 61 Target::new(TransportId::LOCAL, format!("local:oversized-{index}")) 62 .expect("oversized target member") 63 }) 64 .collect(); 65 assert_eq!( 66 TargetSet::new(oversized).unwrap_err(), 67 Error::TargetSetTooLarge 68 ); 69 70 assert_eq!( 71 TargetSet::new(vec![target.clone(), target]).unwrap_err(), 72 Error::DuplicateTargetFingerprint 73 ); 74 } 75 76 #[test] 77 fn transport_target_uri_vectors_match_the_canonical_parser() { 78 let document: Value = serde_json::from_str(include_str!("fixtures/target_uri.v1.json")) 79 .expect("transport target vectors"); 80 let vectors = document["vectors"].as_array().expect("vector array"); 81 82 for vector in vectors { 83 let kind = vector["kind"].as_str().expect("vector kind"); 84 let raw = vector["input"]["uri"].as_str().expect("input URI"); 85 match kind { 86 "transport.target_uri.valid" => assert_eq!( 87 EndpointUri::parse(raw).expect("valid endpoint").as_str(), 88 vector["expected"]["canonical_uri"] 89 .as_str() 90 .expect("canonical URI") 91 ), 92 "transport.target_uri.invalid" => assert!(EndpointUri::parse(raw).is_err()), 93 "transport.nostr_relay_target.valid" => assert_eq!( 94 Target::nostr_relay(raw) 95 .expect("valid relay") 96 .uri() 97 .as_str(), 98 vector["expected"]["canonical_uri"] 99 .as_str() 100 .expect("canonical URI") 101 ), 102 "transport.nostr_relay_target.invalid" => { 103 assert!(Target::nostr_relay(raw).is_err()) 104 } 105 "transport.nostr_relay_private_device.valid" => assert_eq!( 106 Target::nostr_relay_with_policy(raw, TargetNetworkPolicy::PrivateDevice) 107 .expect("valid private-device relay") 108 .uri() 109 .as_str(), 110 vector["expected"]["canonical_uri"] 111 .as_str() 112 .expect("canonical URI") 113 ), 114 "transport.nostr_relay_private_device.invalid" => assert!( 115 Target::nostr_relay_with_policy(raw, TargetNetworkPolicy::PrivateDevice).is_err() 116 ), 117 other => panic!("unknown vector kind {other}"), 118 } 119 } 120 } 121 122 #[test] 123 fn generic_endpoint_parser_covers_opaque_authority_and_scheme_edges() { 124 let schemeless = EndpointUri::parse("transport-target").expect("schemeless URI"); 125 assert_eq!(schemeless.as_str(), "transport-target"); 126 assert_eq!(schemeless.to_string(), "transport-target"); 127 assert_eq!( 128 EndpointUri::parse("RNS:PeerA") 129 .expect("opaque URI") 130 .as_str(), 131 "rns:PeerA" 132 ); 133 assert_eq!( 134 EndpointUri::parse("MESH://Node.Example/path?q=1#frag") 135 .expect("authority URI") 136 .as_str(), 137 "mesh://node.example/path?q=1#frag" 138 ); 139 assert_eq!(EndpointUri::parse(" ").unwrap_err(), Error::EmptyTargetUri); 140 141 for invalid in [ 142 "bad target", 143 " transport-target ", 144 ":target", 145 "1bad:target", 146 "bad_scheme://target", 147 "bad\ttarget", 148 "bad\ntarget", 149 ] { 150 assert_eq!( 151 EndpointUri::parse(invalid).unwrap_err(), 152 Error::InvalidTargetUri, 153 "{invalid:?} must fail" 154 ); 155 } 156 } 157 158 #[test] 159 fn nostr_targets_canonicalize_identity_and_local_websocket_endpoints() { 160 let root = Target::nostr_relay("wss://relay.example").expect("root relay"); 161 let slash = Target::nostr_relay("WSS://RELAY.EXAMPLE/").expect("root slash relay"); 162 let default_port = Target::nostr_relay("wss://relay.example:443").expect("default port relay"); 163 let path = Target::nostr_relay("wss://relay.example/Events").expect("path relay"); 164 let generic = 165 Target::new(TransportId::NOSTR, "wss://relay.example/").expect("generic Nostr target"); 166 167 assert_eq!(root.uri().as_str(), "wss://relay.example"); 168 assert_eq!(root.fingerprint(), slash.fingerprint()); 169 assert_eq!(root.fingerprint(), default_port.fingerprint()); 170 assert_eq!(root.fingerprint(), generic.fingerprint()); 171 assert_ne!(root.fingerprint(), path.fingerprint()); 172 assert_eq!(path.uri().as_str(), "wss://relay.example/Events"); 173 assert_eq!( 174 TargetSet::new(vec![root, slash]).unwrap_err(), 175 Error::DuplicateTargetFingerprint 176 ); 177 178 for (raw, expected) in [ 179 ("ws://LOCALHOST:80/", "ws://localhost"), 180 ("ws://127.0.0.1:7777", "ws://127.0.0.1:7777"), 181 ("ws://[0:0:0:0:0:0:0:1]:7777", "ws://[::1]:7777"), 182 ("wss://[2001:0DB8::1]:443", "wss://[2001:db8::1]"), 183 ( 184 "wss://relay.example:7443/a%20b", 185 "wss://relay.example:7443/a%20b", 186 ), 187 ] { 188 assert_eq!( 189 Target::nostr_relay(raw) 190 .expect("valid relay") 191 .uri() 192 .as_str(), 193 expected 194 ); 195 } 196 } 197 198 #[test] 199 fn nostr_targets_reject_ambiguous_authorities_hosts_ports_and_paths() { 200 let overlong = format!("wss://{}", "a".repeat(ENDPOINT_URI_MAX_BYTES)); 201 let overlong_dns_label = format!("wss://{}.example", "a".repeat(64)); 202 let overlong_host = format!("wss://{}.example", "a.".repeat(124)); 203 assert_eq!(Target::nostr_relay("").unwrap_err(), Error::EmptyTargetUri); 204 let invalid = [ 205 " wss://relay.example", 206 "wss://relay.example ", 207 "https://relay.example", 208 "wss:relay.example", 209 "wss://", 210 "wss://user@relay.example", 211 "wss://user:password@relay.example", 212 "wss://relay.example?subscription=1", 213 "wss://relay.example#fragment", 214 "wss://relay.example\\path", 215 "wss://relay.example:", 216 "wss://relay.example:0", 217 "wss://relay.example:01", 218 "wss://relay.example:65536", 219 "wss://relay.example:999999999999999999999999999999999999", 220 "wss://:443", 221 "wss://[", 222 "wss://[]", 223 "wss://[::1", 224 "wss://[::1]suffix", 225 "wss://[::1]:", 226 "wss://[not-ipv6]", 227 "wss://[::ffff:127.0.0.1x]", 228 "wss://relay[.]example", 229 "wss://127.0.0.01", 230 "wss://127.0.0.256", 231 "wss://127.0.0", 232 "wss://127.0.0.1.2", 233 "wss://-relay.example", 234 "wss://relay-.example", 235 "wss://relay..example", 236 "wss://xn--relay.example", 237 "wss://relay.123", 238 "wss://relay.0x10", 239 "wss://relay.ex_ample", 240 "ws://relay.example", 241 "ws://192.168.1.2", 242 "wss://relay.example/.", 243 "wss://relay.example/..", 244 "wss://relay.example/%2E", 245 "wss://relay.example/%2E%2E", 246 "wss://relay.example/%2e", 247 "wss://relay.example/%", 248 "wss://relay.example/%2", 249 "wss://relay.example/a[b", 250 ]; 251 for raw in invalid.into_iter().chain([ 252 overlong.as_str(), 253 overlong_dns_label.as_str(), 254 overlong_host.as_str(), 255 ]) { 256 assert_eq!( 257 Target::nostr_relay(raw).unwrap_err(), 258 Error::InvalidTargetUri, 259 "{raw:?} must fail" 260 ); 261 } 262 } 263 264 #[test] 265 fn private_device_relay_targets_require_an_explicit_literal_policy() { 266 for (raw, canonical, requires_explicit_policy) in [ 267 ("ws://10.0.0.1:7447", "ws://10.0.0.1:7447", true), 268 ("ws://172.16.0.1", "ws://172.16.0.1", true), 269 ("wss://192.168.1.2", "wss://192.168.1.2", false), 270 ("ws://[FD00::1]:7447", "ws://[fd00::1]:7447", true), 271 ] { 272 assert_eq!( 273 Target::nostr_relay(raw).is_err(), 274 requires_explicit_policy, 275 "{raw}" 276 ); 277 assert_eq!( 278 Target::nostr_relay_with_policy(raw, TargetNetworkPolicy::PrivateDevice) 279 .expect("private-device target") 280 .uri() 281 .as_str(), 282 canonical 283 ); 284 } 285 286 for denied in [ 287 "ws://relay.example", 288 "ws://8.8.8.8", 289 "ws://127.0.0.1", 290 "ws://169.254.1.1", 291 "ws://224.0.0.1", 292 "ws://[::1]", 293 "ws://[fe80::1]", 294 "ws://[ff02::1]", 295 ] { 296 assert!( 297 Target::nostr_relay_with_policy(denied, TargetNetworkPolicy::PrivateDevice).is_err(), 298 "{denied}" 299 ); 300 } 301 } 302 303 #[test] 304 fn target_metadata_identity_and_collection_accessors_are_explicit() { 305 let scope = TargetScope::parse("local").expect("scope"); 306 let label = TargetLabel::parse(" Local node ").expect("label"); 307 let local = 308 Target::local_with_metadata("local:node-1", Some(scope.clone()), Some(label.clone())) 309 .expect("local target"); 310 let relabeled = Target::new_with_metadata( 311 TransportId::LOCAL, 312 "local:node-1", 313 Some(scope), 314 Some(TargetLabel::parse("Renamed node").expect("label")), 315 ) 316 .expect("relabeled target"); 317 let unscoped = Target::local("local:node-1").expect("unscoped target"); 318 319 assert_eq!(local.kind(), &TransportId::LOCAL); 320 assert_eq!(local.uri().as_ref(), "local:node-1"); 321 assert_eq!(local.scope().map(TargetScope::as_str), Some("local")); 322 assert_eq!(local.label().map(TargetLabel::as_str), Some("Local node")); 323 assert_eq!(local.fingerprint(), relabeled.fingerprint()); 324 assert_ne!(local.fingerprint(), unscoped.fingerprint()); 325 326 let set = TargetSet::new(vec![local.clone(), unscoped]).expect("target set"); 327 assert!(set.contains(local.fingerprint())); 328 assert!(!set.is_empty()); 329 assert_eq!(set.targets().len(), 2); 330 let foreign = Target::local("local:foreign").expect("foreign target"); 331 assert!(!set.contains(foreign.fingerprint())); 332 333 assert_eq!(TargetScope::parse("").unwrap_err(), Error::EmptyTargetScope); 334 for value in [" scope", "scope ", "bad scope", "bad/scope", "bad\nscope"] { 335 assert_eq!( 336 TargetScope::parse(value).unwrap_err(), 337 Error::InvalidTargetScope 338 ); 339 } 340 assert_eq!( 341 TargetLabel::parse(" ").unwrap_err(), 342 Error::EmptyTargetLabel 343 ); 344 assert_eq!( 345 TargetLabel::parse("bad\nlabel").unwrap_err(), 346 Error::InvalidTargetLabel 347 ); 348 assert_eq!( 349 TargetFingerprint::parse("abc").unwrap_err(), 350 Error::InvalidTargetFingerprint 351 ); 352 assert_eq!( 353 TargetFingerprint::parse("g".repeat(64)).unwrap_err(), 354 Error::InvalidTargetFingerprint 355 ); 356 let uppercase = TargetFingerprint::parse(local.fingerprint().as_str().to_ascii_uppercase()) 357 .expect("uppercase fingerprint"); 358 assert_eq!(uppercase.as_str(), local.fingerprint().as_str()); 359 assert_eq!(uppercase.to_string(), local.fingerprint().as_str()); 360 assert_eq!( 361 TargetSet::new(Vec::new()).unwrap_err(), 362 Error::EmptyTargetSet 363 ); 364 } 365 366 #[test] 367 #[cfg(feature = "serde")] 368 fn target_deserialization_revalidates_every_canonical_identity_field() { 369 let target = Target::nostr_relay_with_metadata( 370 "wss://relay.example/path", 371 Some(TargetScope::parse("public").expect("scope")), 372 Some(TargetLabel::parse("Relay").expect("label")), 373 ) 374 .expect("target"); 375 let value = serde_json::to_value(&target).expect("target JSON"); 376 assert_eq!( 377 serde_json::from_value::<Target>(value.clone()).expect("target round trip"), 378 target 379 ); 380 381 for (field, forged) in [ 382 ("uri", Value::String("WSS://RELAY.EXAMPLE/path".into())), 383 ("scope", Value::String("bad scope".into())), 384 ("label", Value::String(" Relay ".into())), 385 ("fingerprint", Value::String("A".repeat(64))), 386 ("fingerprint", Value::String("0".repeat(64))), 387 ] { 388 let mut invalid = value.clone(); 389 invalid[field] = forged; 390 assert!( 391 serde_json::from_value::<Target>(invalid).is_err(), 392 "{field}" 393 ); 394 } 395 396 let set = TargetSet::new(vec![target]).expect("target set"); 397 let encoded = serde_json::to_value(&set).expect("target set JSON"); 398 assert_eq!( 399 serde_json::from_value::<TargetSet>(encoded.clone()).expect("set round trip"), 400 set 401 ); 402 let mut duplicate = encoded; 403 let first = duplicate["targets"][0].clone(); 404 duplicate["targets"].as_array_mut().unwrap().push(first); 405 assert!(serde_json::from_value::<TargetSet>(duplicate).is_err()); 406 } 407 408 #[test] 409 #[cfg(feature = "serde")] 410 fn private_device_cleartext_target_round_trip_remains_explicit_and_validated() { 411 let target = 412 Target::nostr_relay_with_policy("ws://[fd00::5]:7447", TargetNetworkPolicy::PrivateDevice) 413 .expect("private-device target"); 414 let value = serde_json::to_value(&target).expect("private-device target JSON"); 415 assert_eq!(value["private_device_cleartext"], Value::Bool(true)); 416 assert_eq!( 417 serde_json::from_value::<Target>(value.clone()).expect("private-device round trip"), 418 target 419 ); 420 421 let mut false_marker = value.clone(); 422 false_marker["private_device_cleartext"] = Value::Bool(false); 423 assert!(serde_json::from_value::<Target>(false_marker).is_err()); 424 425 let mut public_address = value.clone(); 426 public_address["uri"] = Value::String("ws://8.8.8.8:7447".into()); 427 assert!(serde_json::from_value::<Target>(public_address).is_err()); 428 429 let mut named = value; 430 named["uri"] = Value::String("ws://relay.internal:7447".into()); 431 assert!(serde_json::from_value::<Target>(named).is_err()); 432 }