coverage.rs (48816B)
1 #[path = "../src/test_fixtures.rs"] 2 mod test_fixtures; 3 4 use nostr::{Event, EventBuilder, JsonUtil, Keys, SecretKey, Timestamp, UnsignedEvent}; 5 use radroots_nostr_connect::client::{ 6 CancellationToken, Client, Completion, Progress, Receive, Target as ClientTarget, 7 }; 8 use radroots_nostr_connect::message::{ 9 PENDING_CONNECTION_ERROR, PendingConnectionOutcome, REMOTE_CAPABILITY_RELAY_COUNT_MAX, 10 REQUEST_ID_MAX_BYTES, REQUEST_PARAM_COUNT_MAX, REQUEST_PARAM_MAX_BYTES, 11 REQUEST_PARAMS_MAX_BYTES, RESPONSE_ERROR_MAX_BYTES, RESPONSE_RESULT_MAX_BYTES, 12 RemoteSessionCapability, RequestId, RequestMessage, ResponseEnvelope, ResponseValidator, 13 SignedEvent as ConnectSignedEvent, UnsignedEvent as ConnectUnsignedEvent, 14 }; 15 use radroots_nostr_connect::permission::{ 16 PERMISSION_PARAMETER_MAX_BYTES, PERMISSIONS_MAX_BYTES, Permissions, 17 }; 18 use radroots_nostr_connect::uri::{ 19 CLIENT_URL_MAX_BYTES, ClientMetadata, RelayUrl, URI_MAX_BYTES, Uri, 20 }; 21 use radroots_nostr_connect::{Error, Method, Permission, Request, Response}; 22 use serde_json::{Value, json}; 23 use std::str::FromStr; 24 use test_fixtures::{ 25 APP_PRIMARY_HTTPS, CDN_PRIMARY_HTTPS, FIXTURE_ALICE, RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS, 26 RELAY_TERTIARY_WSS, 27 }; 28 29 fn test_public_key() -> radroots_identity::PublicKey { 30 radroots_identity::PublicKey::from_hex(FIXTURE_ALICE.public_key_hex).expect("public key") 31 } 32 33 fn test_keys() -> Keys { 34 let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key"); 35 Keys::new(secret_key) 36 } 37 38 fn encode_uri_component(value: &str) -> String { 39 url::form_urlencoded::byte_serialize(value.as_bytes()).collect() 40 } 41 42 fn logo_url() -> String { 43 format!("{CDN_PRIMARY_HTTPS}/logo.png") 44 } 45 46 fn unsigned_event() -> UnsignedEvent { 47 serde_json::from_value(json!({ 48 "pubkey": test_public_key().to_hex(), 49 "created_at": 1714078911u64, 50 "kind": 1u16, 51 "tags": [], 52 "content": "hello" 53 })) 54 .expect("unsigned event") 55 } 56 57 fn signed_event() -> Event { 58 EventBuilder::text_note("hello world") 59 .custom_created_at(Timestamp::from(1_714_078_911)) 60 .sign_with_keys(&test_keys()) 61 .expect("sign event") 62 } 63 64 fn relay(value: &str) -> RelayUrl { 65 RelayUrl::parse(value).expect("relay") 66 } 67 68 #[test] 69 fn error_method_and_permission_surfaces_cover_public_paths() { 70 let json_error = serde_json::from_str::<Value>("{").expect_err("invalid json"); 71 assert!(matches!( 72 Error::from(json_error), 73 Error::Json(message) if !message.is_empty() 74 )); 75 76 let methods = [ 77 (Method::Connect, "connect"), 78 (Method::GetPublicKey, "get_public_key"), 79 (Method::GetSessionCapability, "get_session_capability"), 80 (Method::SignEvent, "sign_event"), 81 (Method::Nip04Encrypt, "nip04_encrypt"), 82 (Method::Nip04Decrypt, "nip04_decrypt"), 83 (Method::Nip44Encrypt, "nip44_encrypt"), 84 (Method::Nip44Decrypt, "nip44_decrypt"), 85 (Method::Ping, "ping"), 86 (Method::SwitchRelays, "switch_relays"), 87 ]; 88 for (method, raw) in methods { 89 assert_eq!(method.as_str(), raw); 90 assert_eq!(method.to_string(), raw); 91 assert_eq!(Method::from_str(raw).expect("parse method"), method); 92 } 93 assert_eq!( 94 Method::from_str("publish_note").expect("custom method"), 95 Method::custom("publish_note").expect("valid custom NIP-46 method") 96 ); 97 assert!(matches!( 98 Method::from_str(" "), 99 Err(Error::InvalidMethod(value)) if value == " " 100 )); 101 assert_eq!( 102 serde_json::from_str::<Method>("\"do_work\"").expect("deserialize custom method"), 103 Method::custom("do_work").expect("valid custom NIP-46 method") 104 ); 105 assert!( 106 serde_json::from_str::<Method>("123") 107 .expect_err("non-string method") 108 .to_string() 109 .contains("invalid type") 110 ); 111 assert!( 112 serde_json::from_str::<Method>("\"\"") 113 .expect_err("blank method") 114 .to_string() 115 .contains("invalid NIP-46 method") 116 ); 117 118 let simple = Permission::new(Method::Ping); 119 assert_eq!(simple.to_string(), "ping"); 120 let parameterized = Permission::with_parameter(Method::SignEvent, "1059"); 121 assert_eq!(parameterized.to_string(), "sign_event:1059"); 122 assert_eq!( 123 Permission::from_str("sign_event:1059").expect("parse permission"), 124 parameterized 125 ); 126 assert!(matches!( 127 Permission::from_str(" "), 128 Err(Error::InvalidPermission(value)) if value == " " 129 )); 130 assert!(matches!( 131 Permission::from_str("sign_event:"), 132 Err(Error::InvalidPermission(value)) if value == "sign_event:" 133 )); 134 assert!(matches!( 135 Permission::from_str(" :kind"), 136 Err(Error::InvalidMethod(_)) 137 )); 138 139 let empty = Permissions::new(); 140 assert!(empty.is_empty()); 141 assert!(empty.as_slice().is_empty()); 142 assert!(empty.clone().into_vec().is_empty()); 143 assert_eq!( 144 Permissions::from_str(" ").expect("empty permissions"), 145 empty 146 ); 147 148 let permissions = Permissions::from(vec![ 149 Permission::new(Method::Nip44Encrypt), 150 Permission::with_parameter(Method::SignEvent, "13"), 151 ]); 152 assert_eq!(permissions.to_string(), "nip44_encrypt,sign_event:13"); 153 assert_eq!( 154 serde_json::to_string(&permissions).expect("serialize permissions"), 155 "\"nip44_encrypt,sign_event:13\"" 156 ); 157 assert_eq!( 158 serde_json::from_str::<Permissions>("\"nip44_encrypt,sign_event:13\"") 159 .expect("deserialize permissions"), 160 permissions 161 ); 162 assert!( 163 serde_json::from_str::<Permissions>("123") 164 .expect_err("non-string permissions") 165 .to_string() 166 .contains("invalid type") 167 ); 168 assert!(matches!( 169 Permissions::from_str("sign_event:,ping"), 170 Err(Error::InvalidPermission(value)) if value == "sign_event:" 171 )); 172 173 let all_sign_events = Permission::new(Method::SignEvent); 174 assert!(all_sign_events.matches_sign_event_kind(30402)); 175 assert!(all_sign_events.matches_request(&Method::SignEvent, None)); 176 assert!(!all_sign_events.matches_request(&Method::Ping, None)); 177 178 let numeric_sign_event = Permission::with_parameter(Method::SignEvent, "30402"); 179 let kind_prefixed_sign_event = Permission::with_parameter(Method::SignEvent, "kind:30402"); 180 assert!(numeric_sign_event.matches_sign_event_kind(30402)); 181 assert!(kind_prefixed_sign_event.matches_sign_event_kind(30402)); 182 assert!(numeric_sign_event.matches_request(&Method::SignEvent, Some("kind:30402"))); 183 assert!(!numeric_sign_event.matches_sign_event_kind(3040)); 184 assert!( 185 !Permission::with_parameter(Method::SignEvent, "130402").matches_sign_event_kind(30402) 186 ); 187 assert!( 188 !Permission::with_parameter(Method::SignEvent, "not-a-kind") 189 .matches_request(&Method::SignEvent, Some("also-not-a-kind")) 190 ); 191 assert!(!Permission::with_parameter(Method::SignEvent, "kind:").matches_sign_event_kind(30402)); 192 let encrypt_permission = 193 Permission::with_parameter(Method::Nip44Encrypt, test_public_key().to_hex()); 194 assert!( 195 encrypt_permission 196 .matches_request(&Method::Nip44Encrypt, Some(&test_public_key().to_hex())) 197 ); 198 assert!(!encrypt_permission.matches_request(&Method::Nip44Encrypt, None)); 199 200 let typed_permissions = Permissions::from(vec![ 201 Permission::new(Method::Ping), 202 kind_prefixed_sign_event, 203 ]); 204 assert!(typed_permissions.allows_request(&Method::Ping, None)); 205 assert!(typed_permissions.allows_sign_event_kind(30402)); 206 assert!(!typed_permissions.allows_sign_event_kind(0)); 207 } 208 209 #[test] 210 fn uri_surface_covers_rendering_ignored_queries_and_error_paths() { 211 let bunker = Uri::parse(&format!( 212 "bunker://{}?relay={}&foo=bar", 213 FIXTURE_ALICE.public_key_hex, 214 encode_uri_component(RELAY_PRIMARY_WSS), 215 )) 216 .expect("parse bunker"); 217 let bunker_rendered = bunker.to_string(); 218 assert!(bunker_rendered.contains(&format!( 219 "relay={}", 220 encode_uri_component(RELAY_PRIMARY_WSS) 221 ))); 222 assert!(!bunker_rendered.contains("secret=")); 223 224 let minimal_client: Uri = format!( 225 "nostrconnect://{}?relay={}&secret=shared", 226 FIXTURE_ALICE.public_key_hex, 227 encode_uri_component(RELAY_PRIMARY_WSS), 228 ) 229 .parse() 230 .expect("parse minimal client"); 231 let minimal_client_rendered = minimal_client.to_string(); 232 assert!(minimal_client_rendered.contains("secret=shared")); 233 assert!(!minimal_client_rendered.contains("perms=")); 234 assert!(!minimal_client_rendered.contains("name=")); 235 assert!(!minimal_client_rendered.contains("url=")); 236 assert!(!minimal_client_rendered.contains("image=")); 237 238 let metadata_client = Uri::parse(&format!( 239 "nostrconnect://{}?relay={}&secret=shared&perms=ping&name=myc&url={}&image={}&ignored=value", 240 FIXTURE_ALICE.public_key_hex, 241 encode_uri_component(RELAY_PRIMARY_WSS), 242 encode_uri_component(APP_PRIMARY_HTTPS), 243 encode_uri_component(&logo_url()), 244 )) 245 .expect("parse metadata client"); 246 let metadata_rendered = metadata_client.to_string(); 247 assert!(metadata_rendered.contains("perms=ping")); 248 assert!(metadata_rendered.contains("name=myc")); 249 assert!(metadata_rendered.contains(&format!( 250 "url={}", 251 encode_uri_component(&format!("{APP_PRIMARY_HTTPS}/")) 252 ))); 253 assert!(metadata_rendered.contains(&format!("image={}", encode_uri_component(&logo_url())))); 254 255 assert!(matches!( 256 Uri::parse("not a uri"), 257 Err(Error::InvalidUrl { .. }) 258 )); 259 assert!(matches!( 260 Uri::parse("nostrconnect:///path?relay=wss%3A%2F%2Frelay.example.com&secret=abc"), 261 Err(Error::MissingPublicKey) 262 )); 263 assert!(matches!( 264 Uri::parse(&format!("bunker://{}", FIXTURE_ALICE.public_key_hex)), 265 Err(Error::MissingRelay) 266 )); 267 assert!(matches!( 268 Uri::parse(&format!( 269 "nostrconnect://{}?secret=abc", 270 FIXTURE_ALICE.public_key_hex 271 )), 272 Err(Error::MissingRelay) 273 )); 274 assert!(matches!( 275 Uri::parse(&format!( 276 "nostrconnect://{}?relay={}", 277 FIXTURE_ALICE.public_key_hex, 278 encode_uri_component(RELAY_PRIMARY_WSS), 279 )), 280 Err(Error::MissingSecret) 281 )); 282 assert!(matches!( 283 Uri::parse("https://example.com"), 284 Err(Error::InvalidUriScheme(value)) if value == "https" 285 )); 286 assert!(matches!( 287 Uri::parse("nostrconnect://bad-key?relay=wss%3A%2F%2Frelay.example.com&secret=abc"), 288 Err(Error::InvalidPublicKey { .. }) 289 )); 290 assert!(matches!( 291 Uri::parse(&format!( 292 "nostrconnect://{}?relay=http%3A%2F%2Frelay.example.com&secret=abc", 293 FIXTURE_ALICE.public_key_hex 294 )), 295 Err(Error::InvalidRelayUrl { .. }) 296 )); 297 assert!(matches!( 298 Uri::parse(&format!( 299 "nostrconnect://{}?relay={}&secret=abc&url=not-a-url", 300 FIXTURE_ALICE.public_key_hex, 301 encode_uri_component(RELAY_PRIMARY_WSS), 302 )), 303 Err(Error::InvalidClientMetadata { field: "url", .. }) 304 )); 305 assert!(matches!( 306 Uri::parse("bunker://bad-key?relay=wss%3A%2F%2Frelay.example.com"), 307 Err(Error::InvalidPublicKey { .. }) 308 )); 309 assert!(matches!( 310 Uri::parse(&format!( 311 "bunker://{}?relay=http%3A%2F%2Frelay.example.com", 312 FIXTURE_ALICE.public_key_hex 313 )), 314 Err(Error::InvalidRelayUrl { .. }) 315 )); 316 assert!(matches!( 317 Uri::parse(&format!( 318 "nostrconnect://{}?relay={}&secret=abc&perms=sign_event%3A", 319 FIXTURE_ALICE.public_key_hex, 320 encode_uri_component(RELAY_PRIMARY_WSS), 321 )), 322 Err(Error::InvalidPermission(value)) if value == "sign_event:" 323 )); 324 assert!(matches!( 325 Uri::parse(&format!( 326 "nostrconnect://{}?relay={}&secret=abc&image=not-a-url", 327 FIXTURE_ALICE.public_key_hex, 328 encode_uri_component(RELAY_PRIMARY_WSS), 329 )), 330 Err(Error::InvalidClientMetadata { field: "image", .. }) 331 )); 332 assert!(matches!( 333 Uri::parse(&format!( 334 "nostrconnect://{}?relay={}&secret=", 335 FIXTURE_ALICE.public_key_hex, 336 encode_uri_component(RELAY_PRIMARY_WSS), 337 )), 338 Err(Error::MissingSecret) 339 )); 340 } 341 342 #[test] 343 fn client_metadata_rejects_malformed_and_unsafe_display_fields() { 344 let empty = ClientMetadata::default(); 345 assert!(empty.is_display_empty()); 346 let decoded: ClientMetadata = serde_json::from_value(json!({ 347 "requested_permissions": "ping", 348 "name": " client ", 349 "url": APP_PRIMARY_HTTPS, 350 "image": logo_url(), 351 })) 352 .expect("deserialize and normalize client metadata"); 353 assert_eq!(decoded.name.as_deref(), Some("client")); 354 assert_eq!( 355 decoded.url.as_deref(), 356 Some(format!("{APP_PRIMARY_HTTPS}/").as_str()) 357 ); 358 assert!( 359 serde_json::from_value::<ClientMetadata>(json!({ 360 "name": "line\nbreak" 361 })) 362 .is_err() 363 ); 364 for metadata in [ 365 ClientMetadata { 366 name: Some("client".to_owned()), 367 ..empty.clone() 368 }, 369 ClientMetadata { 370 url: Some(APP_PRIMARY_HTTPS.to_owned()), 371 ..empty.clone() 372 }, 373 ClientMetadata { 374 image: Some(logo_url()), 375 ..empty.clone() 376 }, 377 ] { 378 assert!(!metadata.is_display_empty()); 379 } 380 381 assert!(matches!( 382 ClientMetadata::from_connect_param("{"), 383 Err(Error::InvalidClientMetadata { 384 field: "payload", 385 .. 386 }) 387 )); 388 389 for (value, field) in [ 390 ("x".repeat(CLIENT_URL_MAX_BYTES + 1), "url"), 391 ("https://example.com/\n".to_owned(), "url"), 392 ("https://user@example.com".to_owned(), "url"), 393 ("https://:secret@example.com".to_owned(), "image"), 394 ] { 395 let metadata = ClientMetadata { 396 url: (field == "url").then_some(value.clone()), 397 image: (field == "image").then_some(value), 398 ..empty.clone() 399 }; 400 assert!(matches!( 401 metadata.normalized(), 402 Err(Error::InvalidClientMetadata { 403 field: actual, 404 .. 405 }) if actual == field 406 )); 407 } 408 } 409 410 #[test] 411 fn request_surface_covers_variant_methods_serialization_and_validation() { 412 let ping_permission = Permissions::from(vec![Permission::new(Method::Ping)]); 413 414 let requests = vec![ 415 ( 416 Request::Connect { 417 remote_signer_public_key: test_public_key(), 418 secret: None, 419 requested_permissions: Permissions::default(), 420 client_metadata: None, 421 }, 422 Method::Connect, 423 vec![test_public_key().to_hex()], 424 ), 425 ( 426 Request::Connect { 427 remote_signer_public_key: test_public_key(), 428 secret: None, 429 requested_permissions: ping_permission.clone(), 430 client_metadata: None, 431 }, 432 Method::Connect, 433 vec![test_public_key().to_hex(), String::new(), "ping".to_owned()], 434 ), 435 (Request::GetPublicKey, Method::GetPublicKey, Vec::new()), 436 ( 437 Request::GetSessionCapability, 438 Method::GetSessionCapability, 439 Vec::new(), 440 ), 441 ( 442 Request::SignEvent( 443 ConnectUnsignedEvent::from_json(&unsigned_event().as_json()) 444 .expect("unsigned event payload"), 445 ), 446 Method::SignEvent, 447 vec![serde_json::to_string(&unsigned_event()).expect("serialize unsigned event")], 448 ), 449 ( 450 Request::Nip04Encrypt { 451 public_key: test_public_key(), 452 plaintext: "hello".to_owned(), 453 }, 454 Method::Nip04Encrypt, 455 vec![test_public_key().to_hex(), "hello".to_owned()], 456 ), 457 ( 458 Request::Nip04Decrypt { 459 public_key: test_public_key(), 460 ciphertext: "cipher".to_owned(), 461 }, 462 Method::Nip04Decrypt, 463 vec![test_public_key().to_hex(), "cipher".to_owned()], 464 ), 465 ( 466 Request::Nip44Encrypt { 467 public_key: test_public_key(), 468 plaintext: "hello".to_owned(), 469 }, 470 Method::Nip44Encrypt, 471 vec![test_public_key().to_hex(), "hello".to_owned()], 472 ), 473 ( 474 Request::Nip44Decrypt { 475 public_key: test_public_key(), 476 ciphertext: "cipher".to_owned(), 477 }, 478 Method::Nip44Decrypt, 479 vec![test_public_key().to_hex(), "cipher".to_owned()], 480 ), 481 (Request::Ping, Method::Ping, Vec::new()), 482 (Request::SwitchRelays, Method::SwitchRelays, Vec::new()), 483 (Request::Logout, Method::Logout, Vec::new()), 484 ( 485 Request::Custom { 486 method: Method::custom("publish_note").expect("valid custom NIP-46 method"), 487 params: vec!["one".to_owned(), "two".to_owned()], 488 }, 489 Method::custom("publish_note").expect("valid custom NIP-46 method"), 490 vec!["one".to_owned(), "two".to_owned()], 491 ), 492 ]; 493 for (request, method, params) in requests { 494 assert_eq!(request.method(), method); 495 assert_eq!(request.to_params().expect("request params"), params); 496 } 497 498 assert_eq!( 499 Request::from_parts(Method::Connect, vec![test_public_key().to_hex()],) 500 .expect("connect without secret or perms"), 501 Request::Connect { 502 remote_signer_public_key: test_public_key(), 503 secret: None, 504 requested_permissions: Permissions::default(), 505 client_metadata: None, 506 } 507 ); 508 assert_eq!( 509 Request::from_parts( 510 Method::Connect, 511 vec![test_public_key().to_hex(), String::new(), "ping".to_owned()], 512 ) 513 .expect("connect with empty secret"), 514 Request::Connect { 515 remote_signer_public_key: test_public_key(), 516 secret: None, 517 requested_permissions: Permissions::from(vec![Permission::new(Method::Ping),]), 518 client_metadata: None, 519 } 520 ); 521 assert_eq!( 522 Request::from_parts(Method::GetPublicKey, Vec::new(),).expect("get_public_key from parts"), 523 Request::GetPublicKey 524 ); 525 assert_eq!( 526 Request::from_parts(Method::GetSessionCapability, Vec::new(),) 527 .expect("get_session_capability from parts"), 528 Request::GetSessionCapability 529 ); 530 assert_eq!( 531 Request::from_parts( 532 Method::Nip04Encrypt, 533 vec![test_public_key().to_hex(), "hello".to_owned()], 534 ) 535 .expect("nip04 encrypt from parts"), 536 Request::Nip04Encrypt { 537 public_key: test_public_key(), 538 plaintext: "hello".to_owned(), 539 } 540 ); 541 assert_eq!( 542 Request::from_parts( 543 Method::Nip04Decrypt, 544 vec![test_public_key().to_hex(), "cipher".to_owned()], 545 ) 546 .expect("nip04 decrypt from parts"), 547 Request::Nip04Decrypt { 548 public_key: test_public_key(), 549 ciphertext: "cipher".to_owned(), 550 } 551 ); 552 assert_eq!( 553 Request::from_parts( 554 Method::Nip44Encrypt, 555 vec![test_public_key().to_hex(), "hello".to_owned()], 556 ) 557 .expect("nip44 encrypt from parts"), 558 Request::Nip44Encrypt { 559 public_key: test_public_key(), 560 plaintext: "hello".to_owned(), 561 } 562 ); 563 assert_eq!( 564 Request::from_parts( 565 Method::Nip44Decrypt, 566 vec![test_public_key().to_hex(), "cipher".to_owned()], 567 ) 568 .expect("nip44 decrypt from parts"), 569 Request::Nip44Decrypt { 570 public_key: test_public_key(), 571 ciphertext: "cipher".to_owned(), 572 } 573 ); 574 assert_eq!( 575 Request::from_parts(Method::Ping, Vec::new()).expect("ping from parts"), 576 Request::Ping 577 ); 578 assert_eq!( 579 Request::from_parts(Method::SwitchRelays, Vec::new(),).expect("switch relays from parts"), 580 Request::SwitchRelays 581 ); 582 583 for (method, params, expected_error) in [ 584 (Method::GetPublicKey, vec!["oops".to_owned()], "no params"), 585 ( 586 Method::GetSessionCapability, 587 vec!["oops".to_owned()], 588 "no params", 589 ), 590 (Method::SignEvent, Vec::new(), "exactly 1 param"), 591 ( 592 Method::Nip04Encrypt, 593 vec!["only-one".to_owned()], 594 "exactly 2 params", 595 ), 596 ( 597 Method::Nip04Decrypt, 598 vec!["only-one".to_owned()], 599 "exactly 2 params", 600 ), 601 ( 602 Method::Nip44Encrypt, 603 vec!["only-one".to_owned()], 604 "exactly 2 params", 605 ), 606 ( 607 Method::Nip44Decrypt, 608 vec!["only-one".to_owned()], 609 "exactly 2 params", 610 ), 611 (Method::Ping, vec!["oops".to_owned()], "no params"), 612 (Method::SwitchRelays, vec!["oops".to_owned()], "no params"), 613 (Method::Logout, vec!["oops".to_owned()], "no params"), 614 ] { 615 assert!(matches!( 616 Request::from_parts(method, params), 617 Err(Error::InvalidParams { expected, .. }) if expected == expected_error 618 )); 619 } 620 assert!(matches!( 621 Request::from_parts(Method::Connect, Vec::new()), 622 Err(Error::InvalidParams { expected, received, .. }) 623 if expected == "1 to 4 params" && received == 0 624 )); 625 assert!(matches!( 626 Request::from_parts(Method::Connect, vec!["bad-key".to_owned()],), 627 Err(Error::InvalidPublicKey { .. }) 628 )); 629 assert!(matches!( 630 Request::from_parts( 631 Method::Connect, 632 vec![test_public_key().to_hex(), "secret".to_owned(), "sign_event:".to_owned()], 633 ), 634 Err(Error::InvalidPermission(value)) if value == "sign_event:" 635 )); 636 assert!(matches!( 637 Request::from_parts( 638 Method::Connect, 639 vec![ 640 test_public_key().to_hex(), 641 "secret".to_owned(), 642 "ping".to_owned(), 643 "extra".to_owned(), 644 "too-many".to_owned(), 645 ], 646 ), 647 Err(Error::InvalidParams { expected, received, .. }) 648 if expected == "1 to 4 params" && received == 5 649 )); 650 assert!(matches!( 651 Request::from_parts(Method::SignEvent, vec!["not-json".to_owned()],), 652 Err(Error::InvalidRequestPayload { .. }) 653 )); 654 assert!(matches!( 655 Request::from_parts( 656 Method::Nip04Encrypt, 657 vec!["bad-key".to_owned(), "hello".to_owned()], 658 ), 659 Err(Error::InvalidPublicKey { .. }) 660 )); 661 assert!(matches!( 662 Request::from_parts( 663 Method::Nip04Decrypt, 664 vec!["bad-key".to_owned(), "cipher".to_owned()], 665 ), 666 Err(Error::InvalidPublicKey { .. }) 667 )); 668 assert!(matches!( 669 Request::from_parts( 670 Method::Nip44Encrypt, 671 vec!["bad-key".to_owned(), "hello".to_owned()], 672 ), 673 Err(Error::InvalidPublicKey { .. }) 674 )); 675 assert!(matches!( 676 Request::from_parts( 677 Method::Nip44Decrypt, 678 vec!["bad-key".to_owned(), "cipher".to_owned()], 679 ), 680 Err(Error::InvalidPublicKey { .. }) 681 )); 682 683 let custom_message = RequestMessage::new( 684 "req-custom", 685 Request::Custom { 686 method: Method::custom("publish_note").expect("valid custom NIP-46 method"), 687 params: vec!["a".to_owned()], 688 }, 689 ); 690 let encoded = serde_json::to_string(&custom_message).expect("serialize custom request"); 691 let decoded: RequestMessage = 692 serde_json::from_str(&encoded).expect("deserialize custom request"); 693 assert_eq!(decoded, custom_message); 694 assert!( 695 serde_json::from_str::<RequestMessage>("{") 696 .expect_err("invalid request message json") 697 .to_string() 698 .contains("EOF") 699 ); 700 assert!( 701 serde_json::from_str::<RequestMessage>( 702 "{\"id\":\"req\",\"method\":\"get_public_key\",\"params\":[\"oops\"]}", 703 ) 704 .expect_err("invalid request params") 705 .to_string() 706 .contains("invalid parameter count") 707 ); 708 } 709 710 #[test] 711 fn response_surface_covers_success_and_error_paths() { 712 let event = signed_event(); 713 let remote_session_capability = RemoteSessionCapability { 714 user_public_key: test_public_key(), 715 relays: vec![relay(RELAY_PRIMARY_WSS), relay(RELAY_SECONDARY_WSS)], 716 permissions: Permissions::from(vec![ 717 Permission::new(Method::Ping), 718 Permission::with_parameter(Method::SignEvent, "kind:1"), 719 ]), 720 }; 721 let cases = vec![ 722 ( 723 Response::ConnectAcknowledged, 724 Method::Connect, 725 Response::ConnectAcknowledged, 726 ), 727 ( 728 Response::ConnectSecretEcho("secret".to_owned()), 729 Method::Connect, 730 Response::ConnectSecretEcho("secret".to_owned()), 731 ), 732 ( 733 Response::UserPublicKey(test_public_key()), 734 Method::GetPublicKey, 735 Response::UserPublicKey(test_public_key()), 736 ), 737 ( 738 Response::PendingConnection, 739 Method::GetSessionCapability, 740 Response::PendingConnection, 741 ), 742 ( 743 Response::RemoteSessionCapability(remote_session_capability.clone()), 744 Method::GetSessionCapability, 745 Response::RemoteSessionCapability(remote_session_capability.clone()), 746 ), 747 ( 748 Response::SignedEvent( 749 ConnectSignedEvent::from_json(&event.as_json()).expect("signed event payload"), 750 ), 751 Method::SignEvent, 752 Response::SignedEvent( 753 ConnectSignedEvent::from_json(&event.as_json()).expect("signed event payload"), 754 ), 755 ), 756 (Response::Pong, Method::Ping, Response::Pong), 757 ( 758 Response::Nip04Encrypt("cipher".to_owned()), 759 Method::Nip04Encrypt, 760 Response::Nip04Encrypt("cipher".to_owned()), 761 ), 762 ( 763 Response::Nip04Decrypt("plain".to_owned()), 764 Method::Nip04Decrypt, 765 Response::Nip04Decrypt("plain".to_owned()), 766 ), 767 ( 768 Response::Nip44Encrypt("cipher".to_owned()), 769 Method::Nip44Encrypt, 770 Response::Nip44Encrypt("cipher".to_owned()), 771 ), 772 ( 773 Response::Nip44Decrypt("plain".to_owned()), 774 Method::Nip44Decrypt, 775 Response::Nip44Decrypt("plain".to_owned()), 776 ), 777 ( 778 Response::RelayList(vec![relay(RELAY_SECONDARY_WSS), relay(RELAY_TERTIARY_WSS)]), 779 Method::SwitchRelays, 780 Response::RelayList(vec![relay(RELAY_SECONDARY_WSS), relay(RELAY_TERTIARY_WSS)]), 781 ), 782 ( 783 Response::RelayListUnchanged, 784 Method::SwitchRelays, 785 Response::RelayListUnchanged, 786 ), 787 ]; 788 for (response, method, expected) in cases { 789 let envelope = response.into_envelope("req").expect("serialize response"); 790 let parsed = Response::from_envelope(&method, envelope).expect("parse response"); 791 assert_eq!(parsed, expected); 792 } 793 794 let error_envelope = Response::Error { 795 result: Some(json!("partial")), 796 error: "denied".to_owned(), 797 } 798 .into_envelope("req-error") 799 .expect("serialize error response"); 800 assert_eq!(error_envelope.error.as_deref(), Some("denied")); 801 802 let custom_envelope = Response::Custom { 803 result: Some(json!({"ok": true})), 804 error: Some("warning".to_owned()), 805 } 806 .into_envelope("req-custom") 807 .expect("serialize custom response"); 808 assert_eq!(custom_envelope.error.as_deref(), Some("warning")); 809 810 let auth_envelope = Response::AuthUrl("https://auth.example.com/challenge".to_owned()) 811 .into_envelope("req-auth") 812 .expect("serialize auth_url"); 813 assert_eq!( 814 Response::from_envelope(&Method::SignEvent, auth_envelope,).expect("parse auth_url"), 815 Response::AuthUrl("https://auth.example.com/challenge".to_owned()) 816 ); 817 818 assert_eq!( 819 Response::from_envelope( 820 &Method::custom("publish_note").expect("valid custom NIP-46 method"), 821 ResponseEnvelope { 822 id: "req-custom".to_owned(), 823 result: Some(json!("ok")), 824 error: None, 825 }, 826 ) 827 .expect("parse custom response without error"), 828 Response::Custom { 829 result: Some(json!("ok")), 830 error: None, 831 } 832 ); 833 assert_eq!( 834 Response::from_envelope( 835 &Method::custom("publish_note").expect("valid custom NIP-46 method"), 836 ResponseEnvelope { 837 id: "req-custom".to_owned(), 838 result: Some(json!({"ok": true})), 839 error: Some("warning".to_owned()), 840 }, 841 ) 842 .expect("parse custom response"), 843 Response::Custom { 844 result: Some(json!({"ok": true})), 845 error: Some("warning".to_owned()), 846 } 847 ); 848 assert_eq!( 849 Response::from_envelope( 850 &Method::GetPublicKey, 851 ResponseEnvelope { 852 id: "req-pending".to_owned(), 853 result: None, 854 error: Some(PENDING_CONNECTION_ERROR.to_owned()), 855 }, 856 ) 857 .expect("parse typed pending response"), 858 Response::PendingConnection 859 ); 860 assert_eq!( 861 Response::from_envelope( 862 &Method::GetSessionCapability, 863 ResponseEnvelope { 864 id: "req-pending-capability".to_owned(), 865 result: None, 866 error: Some(PENDING_CONNECTION_ERROR.to_owned()), 867 }, 868 ) 869 .expect("parse typed pending capability response"), 870 Response::PendingConnection 871 ); 872 assert_eq!( 873 Response::from_envelope( 874 &Method::GetPublicKey, 875 ResponseEnvelope { 876 id: "req-nonpending-public-key".to_owned(), 877 result: None, 878 error: Some("denied".to_owned()), 879 }, 880 ) 881 .expect("parse non-pending public key error"), 882 Response::Error { 883 result: None, 884 error: "denied".to_owned(), 885 } 886 ); 887 assert_eq!( 888 Response::from_envelope( 889 &Method::GetSessionCapability, 890 ResponseEnvelope { 891 id: "req-capability-error-with-result".to_owned(), 892 result: Some(json!({"code": "retry"})), 893 error: Some("denied".to_owned()), 894 }, 895 ) 896 .expect("parse capability error with result"), 897 Response::Error { 898 result: Some(json!({"code": "retry"})), 899 error: "denied".to_owned(), 900 } 901 ); 902 assert!(matches!( 903 Response::from_envelope( 904 &Method::GetSessionCapability, 905 ResponseEnvelope { 906 id: "req-capability-invalid-result".to_owned(), 907 result: Some(json!({"permissions": "ping"})), 908 error: None, 909 }, 910 ), 911 Err(Error::InvalidResponsePayload { method, .. }) 912 if method == "get_session_capability" 913 )); 914 assert_eq!( 915 Response::from_envelope( 916 &Method::GetSessionCapability, 917 ResponseEnvelope { 918 id: "req-capability-string-result".to_owned(), 919 result: Some(json!( 920 serde_json::to_string(&remote_session_capability) 921 .expect("serialize remote session capability") 922 )), 923 error: None, 924 }, 925 ) 926 .expect("parse stringified capability result"), 927 Response::RemoteSessionCapability(remote_session_capability.clone(),) 928 ); 929 assert!(matches!( 930 Response::from_envelope( 931 &Method::GetSessionCapability, 932 ResponseEnvelope { 933 id: "req-capability-invalid-string".to_owned(), 934 result: Some(json!("{")), 935 error: None, 936 }, 937 ), 938 Err(Error::InvalidResponsePayload { method, .. }) 939 if method == "get_session_capability" 940 )); 941 assert_eq!( 942 Response::from_envelope( 943 &Method::Ping, 944 ResponseEnvelope { 945 id: "req-error".to_owned(), 946 result: Some(json!("partial")), 947 error: Some("denied".to_owned()), 948 }, 949 ) 950 .expect("parse error response"), 951 Response::Error { 952 result: Some(json!("partial")), 953 error: "denied".to_owned(), 954 } 955 ); 956 assert_eq!( 957 Response::from_envelope( 958 &Method::SignEvent, 959 ResponseEnvelope { 960 id: "req-event".to_owned(), 961 result: Some(serde_json::to_value(&event).expect("event value")), 962 error: None, 963 }, 964 ) 965 .expect("parse object event"), 966 Response::SignedEvent( 967 ConnectSignedEvent::from_json(&event.as_json()).expect("signed event payload") 968 ) 969 ); 970 assert_eq!( 971 Response::from_envelope( 972 &Method::SwitchRelays, 973 ResponseEnvelope { 974 id: "req-switch".to_owned(), 975 result: Some(json!("null")), 976 error: None, 977 }, 978 ) 979 .expect("parse string null"), 980 Response::RelayListUnchanged 981 ); 982 assert_eq!( 983 Response::from_envelope( 984 &Method::SwitchRelays, 985 ResponseEnvelope { 986 id: "req-switch".to_owned(), 987 result: Some(json!(format!("[\"{RELAY_SECONDARY_WSS}\"]"))), 988 error: None, 989 }, 990 ) 991 .expect("parse stringified relay list"), 992 Response::RelayList(vec![relay(RELAY_SECONDARY_WSS)]) 993 ); 994 995 assert!(matches!( 996 Response::AuthUrl("not-a-url".to_owned()).into_envelope("req"), 997 Err(Error::InvalidUrl { value, .. }) if value == "[redacted auth URL]" 998 )); 999 assert!(matches!( 1000 Response::from_envelope( 1001 &Method::SignEvent, 1002 ResponseEnvelope { 1003 id: "req-auth".to_owned(), 1004 result: Some(json!("auth_url")), 1005 error: Some("not-a-url".to_owned()), 1006 }, 1007 ), 1008 Err(Error::InvalidUrl { value, .. }) if value == "[redacted auth URL]" 1009 )); 1010 assert!(matches!( 1011 Response::from_envelope( 1012 &Method::GetPublicKey, 1013 ResponseEnvelope { 1014 id: "req-key".to_owned(), 1015 result: Some(json!("bad-key")), 1016 error: None, 1017 }, 1018 ), 1019 Err(Error::InvalidPublicKey { .. }) 1020 )); 1021 assert!(matches!( 1022 Response::from_envelope( 1023 &Method::Connect, 1024 ResponseEnvelope { 1025 id: "req-connect".to_owned(), 1026 result: None, 1027 error: None, 1028 }, 1029 ), 1030 Err(Error::MissingResult) 1031 )); 1032 assert!(matches!( 1033 Response::from_envelope( 1034 &Method::GetPublicKey, 1035 ResponseEnvelope { 1036 id: "req-key".to_owned(), 1037 result: None, 1038 error: None, 1039 }, 1040 ), 1041 Err(Error::MissingResult) 1042 )); 1043 assert!(matches!( 1044 Response::from_envelope( 1045 &Method::Ping, 1046 ResponseEnvelope { 1047 id: "req-ping".to_owned(), 1048 result: Some(json!("nope")), 1049 error: None, 1050 }, 1051 ), 1052 Err(Error::InvalidResponsePayload { .. }) 1053 )); 1054 assert!(matches!( 1055 Response::from_envelope( 1056 &Method::Ping, 1057 ResponseEnvelope { 1058 id: "req-ping".to_owned(), 1059 result: None, 1060 error: None, 1061 }, 1062 ), 1063 Err(Error::MissingResult) 1064 )); 1065 assert!(matches!( 1066 Response::from_envelope( 1067 &Method::Nip04Encrypt, 1068 ResponseEnvelope { 1069 id: "req-nip04".to_owned(), 1070 result: Some(json!(5)), 1071 error: None, 1072 }, 1073 ), 1074 Err(Error::InvalidResponsePayload { .. }) 1075 )); 1076 assert!(matches!( 1077 Response::from_envelope( 1078 &Method::Nip04Encrypt, 1079 ResponseEnvelope { 1080 id: "req-nip04".to_owned(), 1081 result: None, 1082 error: None, 1083 }, 1084 ), 1085 Err(Error::MissingResult) 1086 )); 1087 assert!(matches!( 1088 Response::from_envelope( 1089 &Method::SignEvent, 1090 ResponseEnvelope { 1091 id: "req-event".to_owned(), 1092 result: Some(json!("not-json")), 1093 error: None, 1094 }, 1095 ), 1096 Err(Error::InvalidResponsePayload { .. }) 1097 )); 1098 assert!(matches!( 1099 Response::from_envelope( 1100 &Method::SignEvent, 1101 ResponseEnvelope { 1102 id: "req-event".to_owned(), 1103 result: Some(json!(5)), 1104 error: None, 1105 }, 1106 ), 1107 Err(Error::InvalidResponsePayload { .. }) 1108 )); 1109 assert!(matches!( 1110 Response::from_envelope( 1111 &Method::SignEvent, 1112 ResponseEnvelope { 1113 id: "req-event".to_owned(), 1114 result: None, 1115 error: None, 1116 }, 1117 ), 1118 Err(Error::MissingResult) 1119 )); 1120 assert!(matches!( 1121 Response::from_envelope( 1122 &Method::Nip04Decrypt, 1123 ResponseEnvelope { 1124 id: "req-nip04d".to_owned(), 1125 result: None, 1126 error: None, 1127 }, 1128 ), 1129 Err(Error::MissingResult) 1130 )); 1131 assert!(matches!( 1132 Response::from_envelope( 1133 &Method::Nip44Encrypt, 1134 ResponseEnvelope { 1135 id: "req-nip44e".to_owned(), 1136 result: None, 1137 error: None, 1138 }, 1139 ), 1140 Err(Error::MissingResult) 1141 )); 1142 assert!(matches!( 1143 Response::from_envelope( 1144 &Method::Nip44Decrypt, 1145 ResponseEnvelope { 1146 id: "req-nip44d".to_owned(), 1147 result: None, 1148 error: None, 1149 }, 1150 ), 1151 Err(Error::MissingResult) 1152 )); 1153 assert!(matches!( 1154 Response::from_envelope( 1155 &Method::SwitchRelays, 1156 ResponseEnvelope { 1157 id: "req-switch".to_owned(), 1158 result: Some(json!("[invalid")), 1159 error: None, 1160 }, 1161 ), 1162 Err(Error::InvalidResponsePayload { .. }) 1163 )); 1164 assert!(matches!( 1165 Response::from_envelope( 1166 &Method::SwitchRelays, 1167 ResponseEnvelope { 1168 id: "req-switch".to_owned(), 1169 result: Some(json!([1])), 1170 error: None, 1171 }, 1172 ), 1173 Err(Error::InvalidResponsePayload { .. }) 1174 )); 1175 assert!(matches!( 1176 Response::from_envelope( 1177 &Method::SwitchRelays, 1178 ResponseEnvelope { 1179 id: "req-switch".to_owned(), 1180 result: Some(json!(["http://relay.example.com"])), 1181 error: None, 1182 }, 1183 ), 1184 Err(Error::InvalidRelayUrl { .. }) 1185 )); 1186 assert!(matches!( 1187 Response::from_envelope( 1188 &Method::SwitchRelays, 1189 ResponseEnvelope { 1190 id: "req-switch".to_owned(), 1191 result: Some(json!(5)), 1192 error: None, 1193 }, 1194 ), 1195 Err(Error::InvalidResponsePayload { .. }) 1196 )); 1197 assert!(matches!( 1198 Response::from_envelope( 1199 &Method::Logout, 1200 ResponseEnvelope { 1201 id: "req-logout".to_owned(), 1202 result: Some(json!("not-ack")), 1203 error: None, 1204 }, 1205 ), 1206 Err(Error::InvalidResponsePayload { method, .. }) 1207 if method == "logout" 1208 )); 1209 } 1210 1211 #[test] 1212 fn pending_connection_poll_outcome_uses_typed_variants() { 1213 let remote_session_capability = RemoteSessionCapability { 1214 user_public_key: test_public_key(), 1215 relays: vec![relay(RELAY_PRIMARY_WSS), relay(RELAY_SECONDARY_WSS)], 1216 permissions: Permissions::from(vec![ 1217 Permission::new(Method::Ping), 1218 Permission::with_parameter(Method::SignEvent, "kind:1"), 1219 ]), 1220 }; 1221 1222 assert_eq!( 1223 Response::PendingConnection.into_pending_connection_poll_outcome(), 1224 PendingConnectionOutcome::PendingApproval 1225 ); 1226 1227 assert_eq!( 1228 Response::UserPublicKey(test_public_key()).into_pending_connection_poll_outcome(), 1229 PendingConnectionOutcome::Approved(test_public_key()) 1230 ); 1231 assert_eq!( 1232 Response::RemoteSessionCapability(remote_session_capability.clone()) 1233 .into_pending_connection_poll_outcome(), 1234 PendingConnectionOutcome::ApprovedCapability(remote_session_capability) 1235 ); 1236 1237 assert_eq!( 1238 Response::Error { 1239 result: Some(json!("partial")), 1240 error: "rejected".to_owned(), 1241 } 1242 .into_pending_connection_poll_outcome(), 1243 PendingConnectionOutcome::Rejected { 1244 message: "rejected".to_owned(), 1245 } 1246 ); 1247 assert_eq!( 1248 Response::Error { 1249 result: None, 1250 error: PENDING_CONNECTION_ERROR.to_owned(), 1251 } 1252 .into_pending_connection_poll_outcome(), 1253 PendingConnectionOutcome::PendingApproval 1254 ); 1255 1256 assert_eq!( 1257 Response::AuthUrl("https://auth.example.com/challenge".to_owned()) 1258 .into_pending_connection_poll_outcome(), 1259 PendingConnectionOutcome::AuthChallenge { 1260 url: "https://auth.example.com/challenge".to_owned(), 1261 } 1262 ); 1263 1264 assert!(matches!( 1265 Response::Pong.into_pending_connection_poll_outcome(), 1266 PendingConnectionOutcome::UnexpectedResponse { response } 1267 if response == "pong" 1268 )); 1269 } 1270 1271 #[test] 1272 fn client_and_message_wrappers_cover_redacted_debug_and_value_accessors() { 1273 let target = ClientTarget::try_new( 1274 test_public_key(), 1275 vec![relay(RELAY_PRIMARY_WSS), relay(RELAY_PRIMARY_WSS)], 1276 ) 1277 .unwrap(); 1278 assert_eq!(target.remote_signer_public_key(), test_public_key()); 1279 assert_eq!(target.relays().len(), 1); 1280 let client = Client::generate(target.clone()).unwrap(); 1281 assert_eq!(client.target(), &target); 1282 assert!(client.public_key().is_ok()); 1283 assert!(format!("{client:?}").contains("<redacted>")); 1284 assert!(Client::from_secret("invalid", target).is_err()); 1285 1286 let token = CancellationToken::new(); 1287 assert!(!token.is_cancelled()); 1288 token.cancel(); 1289 assert!(token.is_cancelled()); 1290 assert!(matches!( 1291 Completion::response(Response::Pong), 1292 Completion::Response(_) 1293 )); 1294 assert!(matches!( 1295 Receive::event( 1296 radroots_nostr_connect::client::ClientEvent::from_json(&signed_event().as_json()) 1297 .unwrap() 1298 ), 1299 Receive::Event(_) 1300 )); 1301 assert!( 1302 format!( 1303 "{:?}", 1304 Progress::AuthChallenge { 1305 url: "secret".into() 1306 } 1307 ) 1308 .contains("<redacted>") 1309 ); 1310 1311 let unsigned = ConnectUnsignedEvent::from_json(&unsigned_event().as_json()).unwrap(); 1312 assert_eq!(unsigned.kind(), 1); 1313 assert!(format!("{unsigned:?}").contains("<redacted>")); 1314 let signed = ConnectSignedEvent::from_json(&signed_event().as_json()).unwrap(); 1315 assert!(format!("{signed:?}").contains("<redacted>")); 1316 1317 let envelope = ResponseEnvelope::try_new("request", Some(json!("pong")), None).unwrap(); 1318 assert_eq!(envelope.result(), Some(&json!("pong"))); 1319 assert_eq!(envelope.error(), None); 1320 assert!(format!("{envelope:?}").contains("has_result")); 1321 1322 let capability = RemoteSessionCapability::try_new( 1323 test_public_key(), 1324 vec![relay(RELAY_PRIMARY_WSS)], 1325 Permissions::from(vec![Permission::new(Method::Ping)]), 1326 ) 1327 .unwrap(); 1328 assert_eq!(capability.user_public_key(), test_public_key()); 1329 assert_eq!(capability.relays().len(), 1); 1330 assert!(capability.permissions().allows_request(&Method::Ping, None)); 1331 1332 let responses = [ 1333 Response::ConnectAcknowledged, 1334 Response::ConnectSecretEcho("secret".into()), 1335 Response::LogoutAcknowledged, 1336 Response::PendingConnection, 1337 Response::UserPublicKey(test_public_key()), 1338 Response::RemoteSessionCapability(capability), 1339 Response::SignedEvent(signed), 1340 Response::Pong, 1341 Response::Nip04Encrypt("cipher".into()), 1342 Response::Nip04Decrypt("plain".into()), 1343 Response::Nip44Encrypt("cipher".into()), 1344 Response::Nip44Decrypt("plain".into()), 1345 Response::RelayList(vec![relay(RELAY_PRIMARY_WSS)]), 1346 Response::RelayListUnchanged, 1347 Response::AuthUrl("https://auth.example".into()), 1348 Response::Error { 1349 result: None, 1350 error: "rejected".into(), 1351 }, 1352 Response::Custom { 1353 result: None, 1354 error: None, 1355 }, 1356 ]; 1357 for response in responses { 1358 let debug = format!("{response:?}"); 1359 assert!(debug.contains("<redacted>")); 1360 } 1361 } 1362 1363 #[test] 1364 fn bounded_message_permission_and_uri_validators_cover_each_limit_branch() { 1365 for invalid_id in ["", " request", "line\nbreak"] { 1366 assert!(RequestId::parse(invalid_id).is_err()); 1367 } 1368 assert!(RequestId::parse("x".repeat(REQUEST_ID_MAX_BYTES + 1)).is_err()); 1369 1370 for error in [ 1371 "".to_string(), 1372 "line\nbreak".to_string(), 1373 "x".repeat(RESPONSE_ERROR_MAX_BYTES + 1), 1374 ] { 1375 assert!(ResponseEnvelope::try_new("request", None, Some(error)).is_err()); 1376 } 1377 assert!( 1378 ResponseEnvelope::try_new( 1379 "request", 1380 Some(json!("x".repeat(RESPONSE_RESULT_MAX_BYTES + 1))), 1381 None, 1382 ) 1383 .is_err() 1384 ); 1385 1386 let custom = Method::custom("vendor_action").unwrap(); 1387 for params in [ 1388 vec!["x".into(); REQUEST_PARAM_COUNT_MAX + 1], 1389 vec!["x".repeat(REQUEST_PARAM_MAX_BYTES + 1)], 1390 vec![ 1391 "x".repeat(REQUEST_PARAM_MAX_BYTES); 1392 REQUEST_PARAMS_MAX_BYTES / REQUEST_PARAM_MAX_BYTES + 1 1393 ], 1394 ] { 1395 assert!( 1396 RequestMessage::try_new( 1397 "request", 1398 Request::Custom { 1399 method: custom.clone(), 1400 params 1401 }, 1402 ) 1403 .is_err() 1404 ); 1405 } 1406 1407 for parameter in [ 1408 "".to_string(), 1409 " padded ".to_string(), 1410 "comma,value".to_string(), 1411 "line\nbreak".to_string(), 1412 "x".repeat(PERMISSION_PARAMETER_MAX_BYTES + 1), 1413 ] { 1414 assert!( 1415 Permissions::try_from_vec(vec![Permission::with_parameter(Method::Ping, parameter,)]) 1416 .is_err() 1417 ); 1418 } 1419 assert!(Permissions::from_str(&"p".repeat(PERMISSIONS_MAX_BYTES + 1)).is_err()); 1420 1421 let envelope = ResponseEnvelope::try_new("request", Some(json!("pong")), None).unwrap(); 1422 let mut validator = 1423 ResponseValidator::new(RequestId::parse("request").unwrap(), test_public_key()); 1424 for fingerprint in ["", "line\nbreak"] { 1425 assert!( 1426 validator 1427 .validate(test_public_key(), fingerprint, &envelope) 1428 .is_err() 1429 ); 1430 } 1431 assert!( 1432 validator 1433 .validate( 1434 test_public_key(), 1435 "x".repeat(REQUEST_ID_MAX_BYTES + 1), 1436 &envelope, 1437 ) 1438 .is_err() 1439 ); 1440 1441 assert!(Uri::parse(&"x".repeat(URI_MAX_BYTES + 1)).is_err()); 1442 let duplicate_secret = format!( 1443 "nostrconnect://{}?relay={}&secret=one&secret=two", 1444 FIXTURE_ALICE.public_key_hex, 1445 encode_uri_component(RELAY_PRIMARY_WSS), 1446 ); 1447 assert!(Uri::parse(&duplicate_secret).is_err()); 1448 1449 let too_many_relays = (0..=REMOTE_CAPABILITY_RELAY_COUNT_MAX) 1450 .map(|index| relay(&format!("wss://relay-{index}.example"))) 1451 .collect::<Vec<_>>(); 1452 assert!( 1453 RemoteSessionCapability::try_new(test_public_key(), too_many_relays, Permissions::new(),) 1454 .is_err() 1455 ); 1456 }