lib

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

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 }