focused_availability_outcomes.rs (39836B)
1 use std::error::Error; 2 3 use harvestcircle_application::availability_outcomes::{FetchTargetState, TargetFingerprint}; 4 use harvestcircle_application::{ 5 AvailabilityDiscoveryOutcome, AvailabilityDiscoveryProgress, AvailabilityDiscoveryRequest, 6 AvailabilityDiscoveryState, AvailabilityDiscoveryStopReason, 7 AvailabilityDiscoveryTargetOutcome, AvailabilityDiscoveryUsage, AvailabilityLocalQueryScope, 8 DISCOVERY_FETCH_RAW_RESERVATION_BYTES, MAX_DISCOVERY_FETCH_CALLS, MAX_DISCOVERY_METADATA_BYTES, 9 MAX_DISCOVERY_RETURNED_EVENTS, MAX_DISCOVERY_RETURNED_PER_TARGET, MAX_DISCOVERY_TARGETS, 10 RequestId, SessionGeneration, 11 }; 12 use harvestcircle_domain::error::AvailabilityFailure; 13 use harvestcircle_domain::{ 14 AvailabilityQueryContext, AvailabilityQueryError, PublicKey, SafeError, SafeErrorCode, 15 }; 16 use radroots_transport::outcome::FetchTargetOutcome; 17 18 const AUTHOR_HEX: &str = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; 19 20 fn scope() -> AvailabilityLocalQueryScope { 21 AvailabilityLocalQueryScope::new( 22 PublicKey::from_hex(AUTHOR_HEX).expect("fixed public curve author"), 23 AvailabilityQueryContext::new([1; 32], [2; 32], u64::MAX, u64::MAX) 24 .expect("nonzero structural context and store"), 25 SessionGeneration::from_value(u64::MAX), 26 ) 27 } 28 29 fn fingerprint(index: u8) -> TargetFingerprint { 30 TargetFingerprint::parse(format!("{index:064x}")).expect("fixed public target fingerprint") 31 } 32 33 fn request(targets: Vec<TargetFingerprint>) -> AvailabilityDiscoveryRequest { 34 AvailabilityDiscoveryRequest::new( 35 RequestId::new(7).expect("nonzero request"), 36 scope(), 37 targets, 38 30_000, 39 ) 40 .expect("bounded unique structural request") 41 } 42 43 fn progress( 44 state: FetchTargetState, 45 reason: AvailabilityDiscoveryStopReason, 46 returned: u16, 47 ) -> AvailabilityDiscoveryProgress { 48 AvailabilityDiscoveryProgress::new( 49 AvailabilityDiscoveryState::Requested(state), 50 reason, 51 returned, 52 ) 53 .expect("consistent bounded target progress") 54 } 55 56 fn not_requested() -> AvailabilityDiscoveryProgress { 57 AvailabilityDiscoveryProgress::new( 58 AvailabilityDiscoveryState::NotRequested, 59 AvailabilityDiscoveryStopReason::None, 60 0, 61 ) 62 .expect("explicit unrequested stream") 63 } 64 65 fn target( 66 id: TargetFingerprint, 67 listings: AvailabilityDiscoveryProgress, 68 profiles: AvailabilityDiscoveryProgress, 69 deletions: AvailabilityDiscoveryProgress, 70 ) -> AvailabilityDiscoveryTargetOutcome { 71 AvailabilityDiscoveryTargetOutcome::new(id, listings, profiles, deletions) 72 .expect("one shared bounded target allowance") 73 } 74 75 fn unrequested_target(index: u8) -> AvailabilityDiscoveryTargetOutcome { 76 target( 77 fingerprint(index), 78 not_requested(), 79 not_requested(), 80 not_requested(), 81 ) 82 } 83 84 fn usage(calls: u8, returned: u16) -> AvailabilityDiscoveryUsage { 85 AvailabilityDiscoveryUsage::new(calls, returned).expect("bounded supplied operation accounting") 86 } 87 88 fn assert_code(error: SafeError, code: SafeErrorCode) { 89 assert_eq!(error.code(), code); 90 let standard_error: &dyn Error = &error; 91 assert!(standard_error.source().is_none()); 92 } 93 94 #[test] 95 fn request_admits_empty_and_exact_target_limit() { 96 assert_eq!(MAX_DISCOVERY_TARGETS, 16); 97 for count in [0, 1, 16] { 98 let targets: Vec<_> = (1..=count).map(fingerprint).collect(); 99 let pointer = targets.as_ptr(); 100 let expected: Vec<_> = targets 101 .iter() 102 .map(|value| value.as_str().to_owned()) 103 .collect(); 104 let value = request(targets); 105 assert_eq!(value.targets().len(), usize::from(count)); 106 assert_eq!(value.targets().as_ptr(), pointer); 107 assert_eq!( 108 value 109 .targets() 110 .iter() 111 .map(TargetFingerprint::as_str) 112 .collect::<Vec<_>>(), 113 expected 114 ); 115 } 116 assert_code( 117 AvailabilityDiscoveryRequest::new( 118 RequestId::new(7).expect("request"), 119 scope(), 120 (1..=17).map(fingerprint).collect(), 121 1, 122 ) 123 .expect_err("seventeen selected targets"), 124 SafeErrorCode::AvailabilityCapacity, 125 ); 126 } 127 128 #[test] 129 fn request_refuses_duplicates_and_invalid_deadlines() { 130 for targets in [ 131 vec![fingerprint(1), fingerprint(1)], 132 vec![ 133 fingerprint(1), 134 fingerprint(2), 135 fingerprint(3), 136 fingerprint(1), 137 ], 138 ] { 139 assert_code( 140 AvailabilityDiscoveryRequest::new( 141 RequestId::new(7).expect("request"), 142 scope(), 143 targets, 144 1, 145 ) 146 .expect_err("duplicate canonical target"), 147 SafeErrorCode::AvailabilityInvalidInput, 148 ); 149 } 150 let uppercase = TargetFingerprint::parse("AB".repeat(32)).expect("shared canonicalization"); 151 let lowercase = TargetFingerprint::parse("ab".repeat(32)).expect("canonical target"); 152 assert_eq!(uppercase.as_str(), "ab".repeat(32)); 153 assert_code( 154 AvailabilityDiscoveryRequest::new( 155 RequestId::new(7).expect("request"), 156 scope(), 157 vec![uppercase, lowercase], 158 1, 159 ) 160 .expect_err("canonical aliases are duplicate targets"), 161 SafeErrorCode::AvailabilityInvalidInput, 162 ); 163 for deadline in [1, 30_000] { 164 assert_eq!( 165 AvailabilityDiscoveryRequest::new( 166 RequestId::new(7).expect("request"), 167 scope(), 168 vec![], 169 deadline 170 ) 171 .expect("exact allowed deadline") 172 .deadline_millis(), 173 deadline 174 ); 175 } 176 for deadline in [0, 30_001, u64::MAX] { 177 assert_code( 178 AvailabilityDiscoveryRequest::new( 179 RequestId::new(7).expect("request"), 180 scope(), 181 vec![], 182 deadline, 183 ) 184 .expect_err("outside absolute deadline range"), 185 SafeErrorCode::AvailabilityInvalidInput, 186 ); 187 } 188 } 189 190 #[test] 191 fn request_preserves_scope_and_request_without_signer() { 192 let value = AvailabilityDiscoveryRequest::new( 193 RequestId::new(u64::MAX).expect("full-width request"), 194 scope(), 195 vec![fingerprint(1)], 196 30_000, 197 ) 198 .expect("pure structural values require no signing capability"); 199 assert_eq!(value.request_id().get(), u64::MAX); 200 assert_eq!(value.scope().owner().to_hex(), AUTHOR_HEX); 201 assert_eq!(value.scope().context().context_id(), &[1; 32]); 202 assert_eq!(value.scope().context().store_generation(), &[2; 32]); 203 assert_eq!(value.scope().context().source_revision(), u64::MAX); 204 assert_eq!(value.scope().context().projection_generation(), u64::MAX); 205 assert_eq!(value.scope().session_generation().value(), u64::MAX); 206 assert_eq!(value.deadline_millis(), 30_000); 207 let value = AvailabilityDiscoveryOutcome::new( 208 value, 209 usage(0, 0), 210 vec![unrequested_target(1)], 211 Vec::<u8>::new(), 212 ) 213 .expect("structural unrequested outcome"); 214 assert_eq!(value.request().request_id().get(), u64::MAX); 215 assert_eq!( 216 value.request().scope().context().projection_generation(), 217 u64::MAX 218 ); 219 } 220 221 #[test] 222 fn progress_preserves_every_shared_state_and_empty_distinction() { 223 let unrequested = not_requested(); 224 assert_eq!( 225 unrequested.state(), 226 AvailabilityDiscoveryState::NotRequested 227 ); 228 assert_eq!(unrequested.reason(), AvailabilityDiscoveryStopReason::None); 229 assert_eq!(unrequested.returned(), 0); 230 let cases = [ 231 ( 232 FetchTargetState::Complete, 233 AvailabilityDiscoveryStopReason::None, 234 ), 235 ( 236 FetchTargetState::Partial, 237 AvailabilityDiscoveryStopReason::None, 238 ), 239 ( 240 FetchTargetState::Partial, 241 AvailabilityDiscoveryStopReason::BudgetExhausted, 242 ), 243 ( 244 FetchTargetState::Partial, 245 AvailabilityDiscoveryStopReason::DeadlineExpired, 246 ), 247 ( 248 FetchTargetState::Unavailable, 249 AvailabilityDiscoveryStopReason::None, 250 ), 251 ( 252 FetchTargetState::FailedRetryable, 253 AvailabilityDiscoveryStopReason::None, 254 ), 255 ( 256 FetchTargetState::FailedTerminal, 257 AvailabilityDiscoveryStopReason::None, 258 ), 259 ( 260 FetchTargetState::Cancelled, 261 AvailabilityDiscoveryStopReason::Stopped, 262 ), 263 ]; 264 for (state, reason) in cases { 265 let value = progress(state, reason, 0); 266 assert_eq!(value.state(), AvailabilityDiscoveryState::Requested(state)); 267 assert_eq!(value.reason(), reason); 268 assert_eq!(value.returned(), 0); 269 assert_ne!(value.state(), unrequested.state()); 270 if state != FetchTargetState::Unavailable { 271 for count in [1, 64] { 272 assert_eq!(progress(state, reason, count).returned(), count); 273 } 274 } 275 } 276 assert_ne!( 277 progress( 278 FetchTargetState::Complete, 279 AvailabilityDiscoveryStopReason::None, 280 0 281 ) 282 .state(), 283 progress( 284 FetchTargetState::Unavailable, 285 AvailabilityDiscoveryStopReason::None, 286 0 287 ) 288 .state() 289 ); 290 } 291 292 #[test] 293 fn progress_rejects_inconsistent_stop_reasons_and_counts() { 294 let states = [ 295 AvailabilityDiscoveryState::NotRequested, 296 AvailabilityDiscoveryState::Requested(FetchTargetState::Complete), 297 AvailabilityDiscoveryState::Requested(FetchTargetState::Partial), 298 AvailabilityDiscoveryState::Requested(FetchTargetState::Unavailable), 299 AvailabilityDiscoveryState::Requested(FetchTargetState::FailedRetryable), 300 AvailabilityDiscoveryState::Requested(FetchTargetState::FailedTerminal), 301 AvailabilityDiscoveryState::Requested(FetchTargetState::Cancelled), 302 ]; 303 for state in states { 304 for reason in [ 305 AvailabilityDiscoveryStopReason::None, 306 AvailabilityDiscoveryStopReason::BudgetExhausted, 307 AvailabilityDiscoveryStopReason::DeadlineExpired, 308 AvailabilityDiscoveryStopReason::Stopped, 309 ] { 310 for count in [0, 1, 64, 65] { 311 let valid = match state { 312 AvailabilityDiscoveryState::NotRequested => { 313 reason == AvailabilityDiscoveryStopReason::None && count == 0 314 } 315 AvailabilityDiscoveryState::Requested( 316 FetchTargetState::Complete 317 | FetchTargetState::FailedRetryable 318 | FetchTargetState::FailedTerminal, 319 ) => reason == AvailabilityDiscoveryStopReason::None, 320 AvailabilityDiscoveryState::Requested(FetchTargetState::Partial) => { 321 reason != AvailabilityDiscoveryStopReason::Stopped 322 } 323 AvailabilityDiscoveryState::Requested(FetchTargetState::Unavailable) => { 324 reason == AvailabilityDiscoveryStopReason::None && count == 0 325 } 326 AvailabilityDiscoveryState::Requested(FetchTargetState::Cancelled) => { 327 reason == AvailabilityDiscoveryStopReason::Stopped 328 } 329 }; 330 let result = AvailabilityDiscoveryProgress::new(state, reason, count); 331 if count > 64 { 332 assert_code( 333 result.expect_err("returned cap checked first"), 334 SafeErrorCode::AvailabilityCapacity, 335 ); 336 } else if valid { 337 let value = result.expect("admitted state/reason/count matrix entry"); 338 assert_eq!( 339 (value.state(), value.reason(), value.returned()), 340 (state, reason, count) 341 ); 342 } else { 343 assert_code( 344 result.expect_err("inconsistent progress facts"), 345 SafeErrorCode::AvailabilityInvalidInput, 346 ); 347 } 348 } 349 } 350 } 351 } 352 353 #[test] 354 fn target_streams_remain_independent() { 355 let listings = progress( 356 FetchTargetState::Complete, 357 AvailabilityDiscoveryStopReason::None, 358 10, 359 ); 360 let profiles = progress( 361 FetchTargetState::Partial, 362 AvailabilityDiscoveryStopReason::DeadlineExpired, 363 2, 364 ); 365 let deletions = progress( 366 FetchTargetState::FailedTerminal, 367 AvailabilityDiscoveryStopReason::None, 368 1, 369 ); 370 let value = target(fingerprint(1), listings, profiles, deletions); 371 assert_eq!(value.target(), &fingerprint(1)); 372 assert_eq!(value.listings(), listings); 373 assert_eq!(value.profiles(), profiles); 374 assert_eq!(value.deletions(), deletions); 375 assert_eq!( 376 value.listings().state(), 377 AvailabilityDiscoveryState::Requested(FetchTargetState::Complete) 378 ); 379 assert_eq!( 380 value.profiles().reason(), 381 AvailabilityDiscoveryStopReason::DeadlineExpired 382 ); 383 assert_eq!(value.deletions().returned(), 1); 384 } 385 386 #[test] 387 fn per_target_limit_is_shared_across_all_three_streams() { 388 assert_eq!(MAX_DISCOVERY_RETURNED_PER_TARGET, 64); 389 for counts in [ 390 [0, 0, 0], 391 [1, 0, 0], 392 [64, 0, 0], 393 [0, 64, 0], 394 [0, 0, 64], 395 [21, 21, 21], 396 [22, 21, 21], 397 ] { 398 let stages = counts.map(|count| { 399 progress( 400 FetchTargetState::Partial, 401 AvailabilityDiscoveryStopReason::None, 402 count, 403 ) 404 }); 405 let value = target(fingerprint(1), stages[0], stages[1], stages[2]); 406 assert_eq!( 407 [ 408 value.listings().returned(), 409 value.profiles().returned(), 410 value.deletions().returned() 411 ], 412 counts 413 ); 414 } 415 for counts in [ 416 [23, 21, 21], 417 [64, 1, 0], 418 [0, 64, 1], 419 [1, 0, 64], 420 [64, 64, 64], 421 ] { 422 let stages = counts.map(|count| { 423 progress( 424 FetchTargetState::Partial, 425 AvailabilityDiscoveryStopReason::None, 426 count, 427 ) 428 }); 429 assert_code( 430 AvailabilityDiscoveryTargetOutcome::new( 431 fingerprint(1), 432 stages[0], 433 stages[1], 434 stages[2], 435 ) 436 .expect_err("sum exceeds the one target allowance"), 437 SafeErrorCode::AvailabilityCapacity, 438 ); 439 } 440 } 441 442 #[test] 443 fn usage_enforces_operation_wide_calls_events_and_no_refunds() { 444 assert_eq!(MAX_DISCOVERY_FETCH_CALLS, 4); 445 assert_eq!(MAX_DISCOVERY_RETURNED_EVENTS, 1024); 446 assert_eq!(DISCOVERY_FETCH_RAW_RESERVATION_BYTES, 8_388_608); 447 for calls in 0..=4 { 448 for returned in [0, 1, 1024] { 449 if calls == 0 && returned > 0 { 450 assert_code( 451 AvailabilityDiscoveryUsage::new(calls, returned) 452 .expect_err("returns without any call"), 453 SafeErrorCode::AvailabilityInvalidInput, 454 ); 455 } else { 456 let value = usage(calls, returned); 457 assert_eq!(value.fetch_calls(), calls); 458 assert_eq!(value.returned_events(), returned); 459 assert_eq!(value.reserved_raw_bytes(), u64::from(calls) * 8_388_608); 460 } 461 } 462 } 463 assert_eq!(usage(4, 0).reserved_raw_bytes(), 33_554_432); 464 assert_eq!( 465 usage(4, 1024).reserved_raw_bytes(), 466 usage(4, 0).reserved_raw_bytes() 467 ); 468 for (calls, returned) in [(5, 0), (5, 1024), (4, 1025), (0, 1025), (u8::MAX, u16::MAX)] { 469 assert_code( 470 AvailabilityDiscoveryUsage::new(calls, returned).expect_err("aggregate accounting cap"), 471 SafeErrorCode::AvailabilityCapacity, 472 ); 473 } 474 } 475 476 #[test] 477 fn outcome_preserves_items_after_partial_capped_failed_and_cancelled_targets() { 478 for (state, reason) in [ 479 ( 480 FetchTargetState::Partial, 481 AvailabilityDiscoveryStopReason::None, 482 ), 483 ( 484 FetchTargetState::Partial, 485 AvailabilityDiscoveryStopReason::BudgetExhausted, 486 ), 487 ( 488 FetchTargetState::Partial, 489 AvailabilityDiscoveryStopReason::DeadlineExpired, 490 ), 491 ( 492 FetchTargetState::FailedRetryable, 493 AvailabilityDiscoveryStopReason::None, 494 ), 495 ( 496 FetchTargetState::FailedTerminal, 497 AvailabilityDiscoveryStopReason::None, 498 ), 499 ( 500 FetchTargetState::Cancelled, 501 AvailabilityDiscoveryStopReason::Stopped, 502 ), 503 ] { 504 let value = AvailabilityDiscoveryOutcome::new( 505 request(vec![fingerprint(1)]), 506 usage(1, 3), 507 vec![target( 508 fingerprint(1), 509 progress(state, reason, 3), 510 not_requested(), 511 not_requested(), 512 )], 513 vec![11_u8, 22, 33], 514 ) 515 .expect("valid earlier results survive non-complete target states"); 516 assert_eq!(value.items(), &[11, 22, 33]); 517 assert_eq!(value.usage().returned_events(), 3); 518 assert_eq!( 519 value.listings_state(), 520 AvailabilityDiscoveryState::Requested(state) 521 ); 522 assert_eq!(value.targets()[0].listings().reason(), reason); 523 assert_eq!(value.into_items(), vec![11, 22, 33]); 524 } 525 } 526 527 #[test] 528 fn outcome_refuses_missing_extra_and_duplicate_targets() { 529 for supplied in [ 530 vec![], 531 vec![unrequested_target(2)], 532 vec![unrequested_target(1), unrequested_target(2)], 533 ] { 534 assert_code( 535 AvailabilityDiscoveryOutcome::new( 536 request(vec![fingerprint(1)]), 537 usage(0, 0), 538 supplied, 539 Vec::<u8>::new(), 540 ) 541 .expect_err("missing or unrequested target fingerprint"), 542 SafeErrorCode::AvailabilityScopeMismatch, 543 ); 544 } 545 assert_code( 546 AvailabilityDiscoveryOutcome::new( 547 request(vec![fingerprint(1), fingerprint(2)]), 548 usage(0, 0), 549 vec![unrequested_target(1), unrequested_target(1)], 550 Vec::<u8>::new(), 551 ) 552 .expect_err("duplicate supplied outcome"), 553 SafeErrorCode::AvailabilityInvalidInput, 554 ); 555 assert_code( 556 AvailabilityDiscoveryOutcome::new( 557 request((1..=16).map(fingerprint).collect()), 558 usage(0, 0), 559 (1..=17).map(unrequested_target).collect(), 560 Vec::<u8>::new(), 561 ) 562 .expect_err("target cap before set validation"), 563 SafeErrorCode::AvailabilityCapacity, 564 ); 565 let value = AvailabilityDiscoveryOutcome::new( 566 request(vec![fingerprint(1), fingerprint(2), fingerprint(3)]), 567 usage(0, 0), 568 vec![ 569 unrequested_target(3), 570 unrequested_target(1), 571 unrequested_target(2), 572 ], 573 Vec::<u8>::new(), 574 ) 575 .expect("permuted exact fingerprint set"); 576 assert_eq!( 577 value 578 .targets() 579 .iter() 580 .map(|value| value.target().as_str()) 581 .collect::<Vec<_>>(), 582 vec![ 583 format!("{:064x}", 3), 584 format!("{:064x}", 1), 585 format!("{:064x}", 2) 586 ] 587 ); 588 } 589 590 #[test] 591 fn outcome_binds_returned_count_and_valid_item_count() { 592 for retained in [0, 1, 3] { 593 let value = AvailabilityDiscoveryOutcome::new( 594 request(vec![fingerprint(1)]), 595 usage(1, 3), 596 vec![target( 597 fingerprint(1), 598 progress( 599 FetchTargetState::Partial, 600 AvailabilityDiscoveryStopReason::None, 601 3, 602 ), 603 not_requested(), 604 not_requested(), 605 )], 606 vec![9_u8; retained], 607 ) 608 .expect("valid items may be fewer than returned candidates"); 609 assert_eq!(value.items().len(), retained); 610 } 611 for returned in [0, 2, 4] { 612 assert_code( 613 AvailabilityDiscoveryOutcome::new( 614 request(vec![fingerprint(1)]), 615 usage(1, returned), 616 vec![target( 617 fingerprint(1), 618 progress( 619 FetchTargetState::Partial, 620 AvailabilityDiscoveryStopReason::None, 621 3, 622 ), 623 not_requested(), 624 not_requested(), 625 )], 626 Vec::<u8>::new(), 627 ) 628 .expect_err("exact aggregate returned count"), 629 SafeErrorCode::AvailabilityInvalidInput, 630 ); 631 } 632 assert_code( 633 AvailabilityDiscoveryOutcome::new( 634 request(vec![fingerprint(1)]), 635 usage(1, 3), 636 vec![target( 637 fingerprint(1), 638 progress( 639 FetchTargetState::Partial, 640 AvailabilityDiscoveryStopReason::None, 641 3, 642 ), 643 not_requested(), 644 not_requested(), 645 )], 646 vec![9_u8; 4], 647 ) 648 .expect_err("valid items cannot exceed returned candidates"), 649 SafeErrorCode::AvailabilityInvalidInput, 650 ); 651 assert_code( 652 AvailabilityDiscoveryOutcome::new(request(vec![]), usage(0, 0), vec![], vec![9_u8; 1025]) 653 .expect_err("item cap before empty-source/count validation"), 654 SafeErrorCode::AvailabilityCapacity, 655 ); 656 assert_code( 657 AvailabilityDiscoveryOutcome::new( 658 request(vec![fingerprint(1)]), 659 usage(0, 0), 660 vec![target( 661 fingerprint(1), 662 progress( 663 FetchTargetState::Complete, 664 AvailabilityDiscoveryStopReason::None, 665 0, 666 ), 667 not_requested(), 668 not_requested(), 669 )], 670 Vec::<u8>::new(), 671 ) 672 .expect_err("completion requires a supplied fetch call"), 673 SafeErrorCode::AvailabilityInvalidInput, 674 ); 675 } 676 677 #[test] 678 fn empty_selection_never_fabricates_complete_or_network_work() { 679 let value = 680 AvailabilityDiscoveryOutcome::new(request(vec![]), usage(0, 0), vec![], Vec::<u8>::new()) 681 .expect("explicit no-source selection"); 682 assert_eq!( 683 value.listings_state(), 684 AvailabilityDiscoveryState::NotRequested 685 ); 686 assert_eq!( 687 value.profiles_state(), 688 AvailabilityDiscoveryState::NotRequested 689 ); 690 assert_eq!( 691 value.deletions_state(), 692 AvailabilityDiscoveryState::NotRequested 693 ); 694 assert_eq!(value.usage().reserved_raw_bytes(), 0); 695 assert!(value.targets().is_empty()); 696 assert!(value.items().is_empty()); 697 for (calls, returned, items) in [ 698 (1, 0, vec![]), 699 (1, 1, vec![]), 700 (1, 1, vec![1_u8]), 701 (0, 0, vec![1_u8]), 702 ] { 703 assert_code( 704 AvailabilityDiscoveryOutcome::new( 705 request(vec![]), 706 usage(calls, returned), 707 vec![], 708 items, 709 ) 710 .expect_err("empty selection cannot fabricate work or items"), 711 SafeErrorCode::AvailabilityInvalidInput, 712 ); 713 } 714 assert_code( 715 AvailabilityDiscoveryOutcome::new( 716 request(vec![]), 717 usage(0, 0), 718 vec![unrequested_target(1)], 719 Vec::<u8>::new(), 720 ) 721 .expect_err("no target may be fabricated"), 722 SafeErrorCode::AvailabilityScopeMismatch, 723 ); 724 } 725 726 #[test] 727 fn stream_completeness_requires_every_selected_target() { 728 let states = [ 729 AvailabilityDiscoveryState::NotRequested, 730 AvailabilityDiscoveryState::Requested(FetchTargetState::Complete), 731 AvailabilityDiscoveryState::Requested(FetchTargetState::Partial), 732 AvailabilityDiscoveryState::Requested(FetchTargetState::Unavailable), 733 AvailabilityDiscoveryState::Requested(FetchTargetState::FailedRetryable), 734 AvailabilityDiscoveryState::Requested(FetchTargetState::FailedTerminal), 735 AvailabilityDiscoveryState::Requested(FetchTargetState::Cancelled), 736 ]; 737 for first in states { 738 for second in states { 739 let stage = |state| { 740 AvailabilityDiscoveryProgress::new( 741 state, 742 if state == AvailabilityDiscoveryState::Requested(FetchTargetState::Cancelled) { 743 AvailabilityDiscoveryStopReason::Stopped 744 } else { 745 AvailabilityDiscoveryStopReason::None 746 }, 747 0, 748 ) 749 .expect("zero-count stream state") 750 }; 751 let value = AvailabilityDiscoveryOutcome::new( 752 request(vec![fingerprint(1), fingerprint(2)]), 753 usage(1, 0), 754 vec![ 755 target(fingerprint(1), stage(first), stage(first), stage(first)), 756 target(fingerprint(2), stage(second), stage(second), stage(second)), 757 ], 758 Vec::<u8>::new(), 759 ) 760 .expect("exact selected stream evidence"); 761 let expected = if first == second { 762 first 763 } else { 764 AvailabilityDiscoveryState::Requested(FetchTargetState::Partial) 765 }; 766 assert_eq!(value.listings_state(), expected); 767 assert_eq!(value.profiles_state(), expected); 768 assert_eq!(value.deletions_state(), expected); 769 } 770 } 771 } 772 773 #[test] 774 fn one_stream_failure_never_erases_another_stream_completion() { 775 for interrupted in [ 776 FetchTargetState::Partial, 777 FetchTargetState::Unavailable, 778 FetchTargetState::FailedRetryable, 779 FetchTargetState::FailedTerminal, 780 FetchTargetState::Cancelled, 781 ] { 782 let reason = if interrupted == FetchTargetState::Cancelled { 783 AvailabilityDiscoveryStopReason::Stopped 784 } else { 785 AvailabilityDiscoveryStopReason::None 786 }; 787 for stream in 0..3 { 788 let mut stages = [progress( 789 FetchTargetState::Complete, 790 AvailabilityDiscoveryStopReason::None, 791 0, 792 ); 3]; 793 stages[stream] = progress(interrupted, reason, 0); 794 let value = AvailabilityDiscoveryOutcome::new( 795 request(vec![fingerprint(1)]), 796 usage(1, 0), 797 vec![target(fingerprint(1), stages[0], stages[1], stages[2])], 798 Vec::<u8>::new(), 799 ) 800 .expect("independent stream statuses"); 801 for (index, observed) in [ 802 value.listings_state(), 803 value.profiles_state(), 804 value.deletions_state(), 805 ] 806 .into_iter() 807 .enumerate() 808 { 809 assert_eq!( 810 observed, 811 AvailabilityDiscoveryState::Requested(if index == stream { 812 interrupted 813 } else { 814 FetchTargetState::Complete 815 }) 816 ); 817 } 818 } 819 } 820 } 821 822 #[test] 823 fn outcome_retains_owned_nonclone_items_without_copy() { 824 struct PrivateItem { 825 marker: String, 826 } 827 let items = vec![PrivateItem { 828 marker: "HCAV_PRIVATE_NONDEBUG_ITEM".to_owned(), 829 }]; 830 let pointer = items.as_ptr(); 831 let capacity = items.capacity(); 832 let targets = vec![target( 833 fingerprint(1), 834 progress( 835 FetchTargetState::FailedRetryable, 836 AvailabilityDiscoveryStopReason::None, 837 1, 838 ), 839 not_requested(), 840 not_requested(), 841 )]; 842 let targets_pointer = targets.as_ptr(); 843 let value = AvailabilityDiscoveryOutcome::new( 844 request(vec![fingerprint(1)]), 845 usage(1, 1), 846 targets, 847 items, 848 ) 849 .expect("no item Clone/Debug requirement"); 850 assert_eq!(value.items().as_ptr(), pointer); 851 assert_eq!(value.targets().as_ptr(), targets_pointer); 852 assert_eq!(value.items()[0].marker, "HCAV_PRIVATE_NONDEBUG_ITEM"); 853 assert!(!format!("{value:?}").contains("HCAV_PRIVATE_NONDEBUG_ITEM")); 854 assert!(!value.metadata_json().contains("HCAV_PRIVATE_NONDEBUG_ITEM")); 855 let returned = value.into_items(); 856 assert_eq!(returned.as_ptr(), pointer); 857 assert_eq!(returned.capacity(), capacity); 858 assert_eq!(returned[0].marker, "HCAV_PRIVATE_NONDEBUG_ITEM"); 859 } 860 861 #[test] 862 fn metadata_serialization_has_exact_safe_schema() { 863 let value = AvailabilityDiscoveryOutcome::new( 864 request(vec![fingerprint(1)]), 865 usage(1, 3), 866 vec![target( 867 fingerprint(1), 868 progress( 869 FetchTargetState::Complete, 870 AvailabilityDiscoveryStopReason::None, 871 1, 872 ), 873 progress( 874 FetchTargetState::Partial, 875 AvailabilityDiscoveryStopReason::BudgetExhausted, 876 2, 877 ), 878 not_requested(), 879 )], 880 vec!["HCAV_PRIVATE_ITEM_PAYLOAD"], 881 ) 882 .expect("independently described metadata fixture"); 883 let expected = concat!( 884 "{\"version\":1,\"fetch_calls\":1,\"reserved_raw_bytes\":8388608,\"returned_events\":3,\"retained_items\":1,", 885 "\"listings\":\"complete\",\"profiles\":\"partial\",\"deletions\":\"not_requested\",\"targets\":[", 886 "{\"target\":\"0000000000000000000000000000000000000000000000000000000000000001\",", 887 "\"listings\":{\"state\":\"complete\",\"reason\":\"none\",\"returned\":1},", 888 "\"profiles\":{\"state\":\"partial\",\"reason\":\"budget_exhausted\",\"returned\":2},", 889 "\"deletions\":{\"state\":\"not_requested\",\"reason\":\"none\",\"returned\":0}}]}" 890 ); 891 assert_eq!(value.metadata_json(), expected); 892 assert_eq!(value.metadata_json(), value.metadata_json()); 893 let empty = 894 AvailabilityDiscoveryOutcome::new(request(vec![]), usage(0, 0), vec![], Vec::<u8>::new()) 895 .expect("empty metadata"); 896 assert_eq!( 897 empty.metadata_json(), 898 "{\"version\":1,\"fetch_calls\":0,\"reserved_raw_bytes\":0,\"returned_events\":0,\"retained_items\":0,\"listings\":\"not_requested\",\"profiles\":\"not_requested\",\"deletions\":\"not_requested\",\"targets\":[]}" 899 ); 900 } 901 902 #[test] 903 fn metadata_serialization_is_bounded_at_sixteen_targets() { 904 assert_eq!(MAX_DISCOVERY_METADATA_BYTES, 16_384); 905 let targets: Vec<_> = (1..=16) 906 .rev() 907 .map(|index| { 908 target( 909 fingerprint(index), 910 progress( 911 FetchTargetState::Complete, 912 AvailabilityDiscoveryStopReason::None, 913 32, 914 ), 915 progress( 916 FetchTargetState::Partial, 917 AvailabilityDiscoveryStopReason::DeadlineExpired, 918 16, 919 ), 920 progress( 921 FetchTargetState::FailedRetryable, 922 AvailabilityDiscoveryStopReason::None, 923 16, 924 ), 925 ) 926 }) 927 .collect(); 928 let value = AvailabilityDiscoveryOutcome::new( 929 request((1..=16).map(fingerprint).collect()), 930 usage(4, 1024), 931 targets, 932 vec!["HCAV_PRIVATE_LARGE_RESULT"; 1024], 933 ) 934 .expect("exact full target/result/accounting bounds"); 935 let json = value.metadata_json(); 936 assert!(json.is_ascii()); 937 assert!(json.len() <= 16_384); 938 assert_eq!(json.matches("\"target\":").count(), 16); 939 assert_eq!(json.matches("\"returned\":32").count(), 16); 940 assert_eq!(json.matches("\"returned\":16").count(), 32); 941 assert!(json.starts_with("{\"version\":1,\"fetch_calls\":4,\"reserved_raw_bytes\":33554432,\"returned_events\":1024,\"retained_items\":1024,")); 942 let first = format!("\"target\":\"{:064x}\"", 16); 943 let last = format!("\"target\":\"{:064x}\"", 1); 944 assert!( 945 json.find(&first).expect("supplied first target") 946 < json.find(&last).expect("supplied last target") 947 ); 948 assert!(!json.contains("HCAV_PRIVATE_LARGE_RESULT")); 949 for (state, reason, name, reason_name) in [ 950 ( 951 FetchTargetState::Complete, 952 AvailabilityDiscoveryStopReason::None, 953 "complete", 954 "none", 955 ), 956 ( 957 FetchTargetState::Partial, 958 AvailabilityDiscoveryStopReason::BudgetExhausted, 959 "partial", 960 "budget_exhausted", 961 ), 962 ( 963 FetchTargetState::Partial, 964 AvailabilityDiscoveryStopReason::DeadlineExpired, 965 "partial", 966 "deadline_expired", 967 ), 968 ( 969 FetchTargetState::Unavailable, 970 AvailabilityDiscoveryStopReason::None, 971 "unavailable", 972 "none", 973 ), 974 ( 975 FetchTargetState::FailedRetryable, 976 AvailabilityDiscoveryStopReason::None, 977 "failed_retryable", 978 "none", 979 ), 980 ( 981 FetchTargetState::FailedTerminal, 982 AvailabilityDiscoveryStopReason::None, 983 "failed_terminal", 984 "none", 985 ), 986 ( 987 FetchTargetState::Cancelled, 988 AvailabilityDiscoveryStopReason::Stopped, 989 "cancelled", 990 "stopped", 991 ), 992 ] { 993 let value = AvailabilityDiscoveryOutcome::new( 994 request(vec![fingerprint(1)]), 995 usage(1, 0), 996 vec![target( 997 fingerprint(1), 998 progress(state, reason, 0), 999 not_requested(), 1000 not_requested(), 1001 )], 1002 Vec::<u8>::new(), 1003 ) 1004 .expect("every exact metadata state spelling"); 1005 assert!(value.metadata_json().contains(&format!( 1006 "\"listings\":{{\"state\":\"{name}\",\"reason\":\"{reason_name}\",\"returned\":0}}" 1007 ))); 1008 } 1009 } 1010 1011 #[test] 1012 fn diagnostics_omit_private_payloads_and_remote_messages() { 1013 struct PrivateItem(&'static str); 1014 const PRIVATE_MARKER: &str = "HCAV_PRIVATE_PAYLOAD_MARKER"; 1015 const REMOTE_MARKER: &str = "HCAV_HOSTILE_REMOTE_MESSAGE_\"\n\0🥕"; 1016 let shared = FetchTargetOutcome::new(fingerprint(0xab), FetchTargetState::FailedRetryable) 1017 .with_message(REMOTE_MARKER); 1018 assert_eq!(shared.message(), Some(REMOTE_MARKER)); 1019 let stage = progress(shared.state(), AvailabilityDiscoveryStopReason::None, 1); 1020 let target_value = target( 1021 shared.target().clone(), 1022 stage, 1023 not_requested(), 1024 not_requested(), 1025 ); 1026 let target_debug = format!("{target_value:?}"); 1027 let requested = request(vec![shared.target().clone()]); 1028 let request_debug = format!("{requested:?}"); 1029 let value = AvailabilityDiscoveryOutcome::new( 1030 requested, 1031 usage(1, 1), 1032 vec![target_value], 1033 vec![PrivateItem(PRIVATE_MARKER)], 1034 ) 1035 .expect("retains only selected shared identity/state facts"); 1036 assert_eq!(value.items()[0].0, PRIVATE_MARKER); 1037 let context_bytes = format!("{:?}", [1_u8; 32]); 1038 let store_bytes = format!("{:?}", [2_u8; 32]); 1039 for diagnostic in [ 1040 request_debug, 1041 target_debug, 1042 format!("{value:?}"), 1043 format!("{stage:?}"), 1044 ] { 1045 for forbidden in [ 1046 PRIVATE_MARKER, 1047 "HCAV_HOSTILE_REMOTE_MESSAGE_", 1048 AUTHOR_HEX, 1049 context_bytes.as_str(), 1050 store_bytes.as_str(), 1051 shared.target().as_str(), 1052 "RequestId", 1053 "request_id", 1054 ] { 1055 assert!(!diagnostic.contains(forbidden)); 1056 } 1057 } 1058 let json = value.metadata_json(); 1059 assert!( 1060 json.contains(shared.target().as_str()), 1061 "metadata admits only the bounded canonical public target identity" 1062 ); 1063 for forbidden in [ 1064 PRIVATE_MARKER, 1065 "HCAV_HOSTILE_REMOTE_MESSAGE_", 1066 AUTHOR_HEX, 1067 "request_id", 1068 "context_id", 1069 "store_generation", 1070 "scope", 1071 "message", 1072 "endpoint", 1073 "\"events\":", 1074 "payload", 1075 ] { 1076 assert!(!json.contains(forbidden)); 1077 } 1078 } 1079 1080 #[test] 1081 fn query_errors_map_to_static_distinct_availability_errors() { 1082 for (source, expected_code, expected_message) in [ 1083 ( 1084 AvailabilityQueryError::InvalidInput, 1085 SafeErrorCode::AvailabilityInvalidInput, 1086 "The availability request is invalid.", 1087 ), 1088 ( 1089 AvailabilityQueryError::InputTooLarge, 1090 SafeErrorCode::AvailabilityInvalidInput, 1091 "The availability request is invalid.", 1092 ), 1093 ( 1094 AvailabilityQueryError::ScopeMismatch, 1095 SafeErrorCode::AvailabilityScopeMismatch, 1096 "The availability request belongs to another scope.", 1097 ), 1098 ( 1099 AvailabilityQueryError::StaleQuery, 1100 SafeErrorCode::AvailabilityStaleQuery, 1101 "The availability query is stale.", 1102 ), 1103 ( 1104 AvailabilityQueryError::Capacity, 1105 SafeErrorCode::AvailabilityCapacity, 1106 "The availability operation reached its capacity.", 1107 ), 1108 ] { 1109 let value = SafeError::from(source); 1110 assert_eq!(value.code(), expected_code); 1111 assert_eq!(value.message().as_str(), expected_message); 1112 assert_eq!(value.to_string(), expected_message); 1113 } 1114 } 1115 1116 #[test] 1117 fn availability_failures_have_fixed_public_messages() { 1118 for (source, expected_code, expected_message) in [ 1119 ( 1120 AvailabilityFailure::InvalidInput, 1121 SafeErrorCode::AvailabilityInvalidInput, 1122 "The availability request is invalid.", 1123 ), 1124 ( 1125 AvailabilityFailure::UnsupportedProfile, 1126 SafeErrorCode::AvailabilityUnsupportedProfile, 1127 "This availability profile is unsupported.", 1128 ), 1129 ( 1130 AvailabilityFailure::ScopeMismatch, 1131 SafeErrorCode::AvailabilityScopeMismatch, 1132 "The availability request belongs to another scope.", 1133 ), 1134 ( 1135 AvailabilityFailure::StaleQuery, 1136 SafeErrorCode::AvailabilityStaleQuery, 1137 "The availability query is stale.", 1138 ), 1139 ( 1140 AvailabilityFailure::Capacity, 1141 SafeErrorCode::AvailabilityCapacity, 1142 "The availability operation reached its capacity.", 1143 ), 1144 ( 1145 AvailabilityFailure::Unavailable, 1146 SafeErrorCode::AvailabilityUnavailable, 1147 "Availability discovery is unavailable.", 1148 ), 1149 ] { 1150 let value = SafeError::from(source); 1151 assert_eq!(value.code(), expected_code); 1152 assert_eq!(value.message().as_str(), expected_message); 1153 assert_eq!(value.to_string(), expected_message); 1154 assert!(!format!("{value:?}").contains("HCAV_HOSTILE_REMOTE_MESSAGE")); 1155 let copied = value; 1156 assert_eq!(copied, value); 1157 let standard_error: &dyn Error = &value; 1158 assert!(standard_error.source().is_none()); 1159 } 1160 }