1use crate::{Epoch, LOGICAL_MAX, PHYSICAL_MS_MAX, Timestamp};
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub struct WindowGrant {
29 physical_ms: u64,
30 logical_start: u32,
31 count: u32,
32 epoch: Epoch,
33}
34
35impl WindowGrant {
36 pub fn try_new(
48 physical_ms: u64,
49 logical_start: u32,
50 count: u32,
51 epoch: Epoch,
52 ) -> Result<Self, CoreError> {
53 if count == 0 {
54 return Err(CoreError::InvalidCount(0));
55 }
56 let last_logical =
57 logical_start
58 .checked_add(count - 1)
59 .ok_or(CoreError::LogicalRangeOutOfRange {
60 logical_start,
61 count,
62 })?;
63 Timestamp::try_pack(physical_ms, last_logical).map_err(|err| match err {
64 crate::TimestampError::PhysicalMsOutOfRange { physical_ms, .. } => {
65 CoreError::PhysicalMsOutOfRange(physical_ms)
66 }
67 crate::TimestampError::LogicalOutOfRange { .. } => CoreError::LogicalRangeOutOfRange {
68 logical_start,
69 count,
70 },
71 })?;
72 Ok(WindowGrant {
73 physical_ms,
74 logical_start,
75 count,
76 epoch,
77 })
78 }
79
80 pub fn physical_ms(&self) -> u64 {
81 self.physical_ms
82 }
83 pub fn logical_start(&self) -> u32 {
84 self.logical_start
85 }
86 pub fn count(&self) -> u32 {
87 self.count
88 }
89 pub fn epoch(&self) -> Epoch {
90 self.epoch
91 }
92
93 pub fn first(&self) -> Timestamp {
97 Timestamp::pack(self.physical_ms, self.logical_start)
98 }
99 pub fn last(&self) -> Timestamp {
104 Timestamp::pack(self.physical_ms, self.logical_start + self.count - 1)
105 }
106}
107
108#[derive(Debug, thiserror::Error, PartialEq, Eq)]
109pub enum CoreError {
110 #[error("not leader")]
111 NotLeader,
112 #[error("window exhausted; caller must extend before retrying")]
113 WindowExhausted,
114 #[error("invalid count: {0}")]
115 InvalidCount(u32),
116 #[error("physical_ms {0} exceeds 46-bit maximum")]
117 PhysicalMsOutOfRange(u64),
118 #[error("logical range [{logical_start}, +{count}) exceeds the 18-bit logical field")]
119 LogicalRangeOutOfRange { logical_start: u32, count: u32 },
120 #[error(
121 "invalid leadership window: fence_floor {fence_floor} exceeds committed_ceiling {committed_ceiling}"
122 )]
123 InvalidLeadershipWindow {
124 fence_floor: u64,
125 committed_ceiling: u64,
126 },
127 #[error(
128 "window extension overflow: max(floor {floor}, now_ms {now_ms}) + ahead_ms {ahead_ms} exceeds u64::MAX"
129 )]
130 WindowExtensionOverflow {
131 floor: u64,
132 now_ms: u64,
133 ahead_ms: u64,
134 },
135}
136
137#[derive(Copy, Clone, Debug, PartialEq, Eq)]
146pub enum CommitOutcome {
147 Applied(u64),
149 Ignored(IgnoreReason),
151}
152
153#[derive(Copy, Clone, Debug, PartialEq, Eq)]
162pub enum IgnoreReason {
163 NotLeader,
165 EpochMismatch { expected: Epoch, current: Epoch },
168 NotAdvanced { persisted: u64, committed: u64 },
171}
172
173#[derive(Debug)]
174enum State {
175 NotLeader,
176 Leader {
177 epoch: Epoch,
178 committed_high_water: u64,
181 next_physical_ms: u64,
185 next_logical: u32,
187 },
188}
189
190pub struct Allocator {
191 state: State,
192}
193
194impl Allocator {
195 pub fn new() -> Self {
196 Allocator {
197 state: State::NotLeader,
198 }
199 }
200
201 pub fn try_on_leadership_gained(
213 &mut self,
214 fence_floor: u64,
215 committed_ceiling: u64,
216 epoch: Epoch,
217 ) -> Result<(), CoreError> {
218 if fence_floor > PHYSICAL_MS_MAX {
219 return Err(CoreError::PhysicalMsOutOfRange(fence_floor));
220 }
221 if committed_ceiling > PHYSICAL_MS_MAX {
222 return Err(CoreError::PhysicalMsOutOfRange(committed_ceiling));
223 }
224 if committed_ceiling < fence_floor {
225 return Err(CoreError::InvalidLeadershipWindow {
226 fence_floor,
227 committed_ceiling,
228 });
229 }
230 self.state = State::Leader {
231 epoch,
232 committed_high_water: committed_ceiling,
233 next_physical_ms: fence_floor,
234 next_logical: 0,
235 };
236 Ok(())
237 }
238
239 pub fn on_leadership_lost(&mut self) {
240 self.state = State::NotLeader;
241 }
242
243 pub fn is_leader(&self) -> bool {
244 matches!(self.state, State::Leader { .. })
245 }
246
247 pub fn epoch(&self) -> Option<Epoch> {
248 match self.state {
249 State::Leader { epoch, .. } => Some(epoch),
250 State::NotLeader => None,
251 }
252 }
253
254 fn project_grant(
264 next_physical_ms: u64,
265 next_logical: u32,
266 committed_high_water: u64,
267 now_ms: u64,
268 count: u32,
269 ) -> Result<(u64, u32), CoreError> {
270 let mut physical_ms = next_physical_ms;
271 let mut logical = next_logical;
272
273 if now_ms > physical_ms {
276 physical_ms = now_ms;
277 logical = 0;
278 }
279
280 if logical as u64 + count as u64 > LOGICAL_MAX as u64 + 1 {
283 physical_ms += 1;
284 logical = 0;
285 }
286
287 if physical_ms > PHYSICAL_MS_MAX {
288 return Err(CoreError::PhysicalMsOutOfRange(physical_ms));
289 }
290
291 if physical_ms > committed_high_water {
294 return Err(CoreError::WindowExhausted);
295 }
296
297 Ok((physical_ms, logical))
298 }
299
300 pub fn try_grant(&mut self, now_ms: u64, count: u32) -> Result<WindowGrant, CoreError> {
308 if count == 0 {
309 return Err(CoreError::InvalidCount(0));
310 }
311 if count > LOGICAL_MAX + 1 {
312 return Err(CoreError::InvalidCount(count));
313 }
314 let State::Leader {
315 epoch,
316 committed_high_water,
317 next_physical_ms,
318 next_logical,
319 } = &mut self.state
320 else {
321 return Err(CoreError::NotLeader);
322 };
323
324 let (physical_ms, logical_start) = Self::project_grant(
325 *next_physical_ms,
326 *next_logical,
327 *committed_high_water,
328 now_ms,
329 count,
330 )?;
331
332 let grant = WindowGrant::try_new(physical_ms, logical_start, count, *epoch)?;
338 *next_physical_ms = physical_ms;
339 *next_logical = logical_start + count;
340 Ok(grant)
341 }
342
343 pub fn would_grant(&self, now_ms: u64, count: u32) -> bool {
351 if count == 0 || count > LOGICAL_MAX + 1 {
352 return false;
353 }
354 let State::Leader {
355 committed_high_water,
356 next_physical_ms,
357 next_logical,
358 ..
359 } = &self.state
360 else {
361 return false;
362 };
363
364 Self::project_grant(
365 *next_physical_ms,
366 *next_logical,
367 *committed_high_water,
368 now_ms,
369 count,
370 )
371 .is_ok()
372 }
373
374 pub fn try_prepare_window_extension(
386 &self,
387 now_ms: u64,
388 ahead_ms: u64,
389 ) -> Result<u64, CoreError> {
390 match &self.state {
391 State::NotLeader => Err(CoreError::NotLeader),
392 State::Leader {
393 committed_high_water,
394 ..
395 } => {
396 let floor = committed_high_water
397 .checked_add(1)
398 .ok_or(CoreError::PhysicalMsOutOfRange(*committed_high_water))?;
399 let requested = core::cmp::max(floor, now_ms).checked_add(ahead_ms).ok_or(
400 CoreError::WindowExtensionOverflow {
401 floor,
402 now_ms,
403 ahead_ms,
404 },
405 )?;
406 if requested > PHYSICAL_MS_MAX {
407 return Err(CoreError::PhysicalMsOutOfRange(requested));
408 }
409 Ok(requested)
410 }
411 }
412 }
413
414 pub fn try_commit_window_extension(
430 &mut self,
431 persisted_high_water: u64,
432 expected_epoch: Epoch,
433 ) -> Result<CommitOutcome, CoreError> {
434 if persisted_high_water > PHYSICAL_MS_MAX {
435 return Err(CoreError::PhysicalMsOutOfRange(persisted_high_water));
436 }
437 let State::Leader {
438 epoch,
439 committed_high_water,
440 ..
441 } = &mut self.state
442 else {
443 return Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader));
444 };
445 if *epoch != expected_epoch {
449 return Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
450 expected: expected_epoch,
451 current: *epoch,
452 }));
453 }
454 if persisted_high_water <= *committed_high_water {
455 return Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
456 persisted: persisted_high_water,
457 committed: *committed_high_water,
458 }));
459 }
460 *committed_high_water = persisted_high_water;
461 Ok(CommitOutcome::Applied(persisted_high_water))
462 }
463}
464
465impl Default for Allocator {
466 fn default() -> Self {
467 Self::new()
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn new_allocator_is_not_leader() {
477 let allocator = Allocator::new();
478 assert!(!allocator.is_leader());
479 assert_eq!(allocator.epoch(), None);
480 }
481
482 #[test]
483 fn on_leadership_gained_sets_epoch() {
484 let mut allocator = Allocator::new();
485 allocator
486 .try_on_leadership_gained(1000, 5000, Epoch(5))
487 .unwrap();
488 assert!(allocator.is_leader());
489 assert_eq!(allocator.epoch(), Some(Epoch(5)));
490 }
491
492 #[test]
493 fn try_on_leadership_gained_rejects_out_of_range_window() {
494 let mut allocator = Allocator::new();
495 assert_eq!(
496 allocator.try_on_leadership_gained(PHYSICAL_MS_MAX + 1, PHYSICAL_MS_MAX + 1, Epoch(5)),
497 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
498 );
499 assert_eq!(
501 allocator.try_on_leadership_gained(1_000, PHYSICAL_MS_MAX + 1, Epoch(5)),
502 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
503 );
504 assert_eq!(
505 allocator.try_on_leadership_gained(5000, 4000, Epoch(5)),
506 Err(CoreError::InvalidLeadershipWindow {
507 fence_floor: 5000,
508 committed_ceiling: 4000
509 })
510 );
511 }
512
513 #[test]
514 fn on_leadership_lost_clears_state() {
515 let mut allocator = Allocator::new();
516 allocator
517 .try_on_leadership_gained(1000, 5000, Epoch(5))
518 .unwrap();
519 allocator.on_leadership_lost();
520 assert!(!allocator.is_leader());
521 assert_eq!(allocator.epoch(), None);
522 }
523
524 #[test]
525 fn try_grant_not_leader() {
526 let mut allocator = Allocator::new();
527 assert_eq!(allocator.try_grant(1000, 1), Err(CoreError::NotLeader));
528 }
529
530 #[test]
531 fn try_grant_zero_count() {
532 let mut allocator = Allocator::new();
533 allocator
534 .try_on_leadership_gained(1000, 5000, Epoch(1))
535 .unwrap();
536 assert_eq!(
537 allocator.try_grant(1000, 0),
538 Err(CoreError::InvalidCount(0))
539 );
540 }
541
542 #[test]
543 fn try_grant_oversized_count() {
544 let mut allocator = Allocator::new();
545 allocator
546 .try_on_leadership_gained(1000, 5000, Epoch(1))
547 .unwrap();
548 let oversized = LOGICAL_MAX + 2;
549 assert_eq!(
550 allocator.try_grant(1000, oversized),
551 Err(CoreError::InvalidCount(oversized))
552 );
553 }
554
555 #[test]
556 fn try_grant_above_committed_is_window_exhausted() {
557 let mut allocator = Allocator::new();
560 allocator
562 .try_on_leadership_gained(5_000, 5_000, Epoch(1))
563 .unwrap();
564 allocator.try_grant(4_999, 1).unwrap();
566 assert_eq!(
568 allocator.try_grant(5_001, 1),
569 Err(CoreError::WindowExhausted)
570 );
571 }
572
573 #[test]
574 fn failed_try_grant_does_not_advance_state() {
575 let mut allocator = Allocator::new();
579 allocator
581 .try_on_leadership_gained(1_000, 1_000, Epoch(1))
582 .unwrap();
583 assert_eq!(
585 allocator.try_grant(5_000, 1),
586 Err(CoreError::WindowExhausted)
587 );
588 let target = allocator.try_prepare_window_extension(2_000, 0).unwrap();
590 assert_eq!(target, 2_000); allocator
592 .try_commit_window_extension(target, Epoch(1))
593 .unwrap();
594 let grant = allocator.try_grant(2_000, 1).unwrap();
599 assert_eq!(grant.physical_ms, 2_000);
600 assert_eq!(grant.logical_start, 0);
601 }
602
603 #[test]
604 fn try_grant_after_gain_serves_immediately() {
605 let mut allocator = Allocator::new();
608 allocator
609 .try_on_leadership_gained(5_000, 10_000, Epoch(1))
610 .unwrap();
611 let grant = allocator.try_grant(1_000, 1).unwrap();
612 assert_eq!(grant.physical_ms, 5_000);
614 assert_eq!(grant.logical_start, 0);
615 assert_eq!(grant.epoch, Epoch(1));
616 }
617
618 #[test]
619 fn prepare_window_extension_not_leader() {
620 let allocator = Allocator::new();
623 assert_eq!(
624 allocator.try_prepare_window_extension(1000, 3000),
625 Err(CoreError::NotLeader)
626 );
627 }
628
629 #[test]
630 fn prepare_window_extension_uses_now_ms_when_ahead_of_high_water() {
631 let mut allocator = Allocator::new();
632 allocator
633 .try_on_leadership_gained(1000, 1000, Epoch(1))
634 .unwrap();
635 let target = allocator.try_prepare_window_extension(2000, 3000).unwrap();
636 assert_eq!(target, 5000); }
638
639 #[test]
640 fn prepare_window_extension_uses_high_water_floor_when_clock_behind() {
641 let mut allocator = Allocator::new();
642 allocator
643 .try_on_leadership_gained(10_000, 10_000, Epoch(1))
644 .unwrap();
645 let target = allocator.try_prepare_window_extension(500, 3000).unwrap();
646 assert_eq!(target, 13_001);
648 }
649
650 #[test]
651 fn prepare_window_extension_rejects_out_of_range_target() {
652 let mut allocator = Allocator::new();
653 allocator
654 .try_on_leadership_gained(PHYSICAL_MS_MAX, PHYSICAL_MS_MAX, Epoch(1))
655 .unwrap();
656 assert_eq!(
657 allocator.try_prepare_window_extension(PHYSICAL_MS_MAX, 1),
658 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 2))
659 );
660 }
661
662 #[test]
663 fn prepare_window_extension_overflow_names_all_operands() {
664 let mut allocator = Allocator::new();
669 allocator
670 .try_on_leadership_gained(1_000, 1_000, Epoch(1))
671 .unwrap();
672 assert_eq!(
673 allocator.try_prepare_window_extension(u64::MAX, 1),
674 Err(CoreError::WindowExtensionOverflow {
675 floor: 1_001,
676 now_ms: u64::MAX,
677 ahead_ms: 1,
678 })
679 );
680 }
681
682 #[test]
683 fn commit_then_try_grant_succeeds() {
684 let mut allocator = Allocator::new();
685 allocator
686 .try_on_leadership_gained(1000, 1000, Epoch(7))
687 .unwrap();
688 let target = allocator.try_prepare_window_extension(1000, 3000).unwrap();
689 assert_eq!(
690 allocator.try_commit_window_extension(target, Epoch(7)),
691 Ok(CommitOutcome::Applied(target))
692 );
693 let grant = allocator.try_grant(1000, 5).unwrap();
694 assert_eq!(grant.count, 5);
695 assert_eq!(grant.logical_start, 0);
696 assert_eq!(grant.epoch, Epoch(7));
697 assert!(grant.physical_ms <= target);
699 }
700
701 #[test]
702 fn commit_with_lower_value_is_ignored() {
703 let mut allocator = Allocator::new();
704 allocator
705 .try_on_leadership_gained(1000, 1000, Epoch(1))
706 .unwrap();
707 assert_eq!(
708 allocator.try_commit_window_extension(5000, Epoch(1)),
709 Ok(CommitOutcome::Applied(5000))
710 );
711 assert_eq!(
714 allocator.try_commit_window_extension(3000, Epoch(1)),
715 Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
716 persisted: 3000,
717 committed: 5000,
718 }))
719 );
720 let grant = allocator.try_grant(4500, 1).unwrap();
722 assert_eq!(grant.physical_ms, 4500);
723 }
724
725 #[test]
726 fn commit_with_equal_value_is_ignored_not_applied() {
727 let mut allocator = Allocator::new();
730 allocator
731 .try_on_leadership_gained(1000, 5000, Epoch(1))
732 .unwrap();
733 assert_eq!(
734 allocator.try_commit_window_extension(5000, Epoch(1)),
735 Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
736 persisted: 5000,
737 committed: 5000,
738 }))
739 );
740 }
741
742 #[test]
743 fn commit_rejects_out_of_range_high_water() {
744 let mut allocator = Allocator::new();
745 allocator
746 .try_on_leadership_gained(1000, 1000, Epoch(1))
747 .unwrap();
748 assert_eq!(
749 allocator.try_commit_window_extension(PHYSICAL_MS_MAX + 1, Epoch(1)),
750 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
751 );
752 }
753
754 #[test]
755 fn try_grant_rejects_out_of_range_clock() {
756 let mut allocator = Allocator::new();
757 allocator
758 .try_on_leadership_gained(1000, PHYSICAL_MS_MAX, Epoch(1))
759 .unwrap();
760 assert_eq!(
761 allocator.try_grant(PHYSICAL_MS_MAX + 1, 1),
762 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
763 );
764 }
765
766 #[test]
767 fn commit_at_wrong_epoch_is_silently_dropped() {
768 let mut allocator = Allocator::new();
769 allocator
771 .try_on_leadership_gained(1000, 1000, Epoch(5))
772 .unwrap();
773 assert_eq!(
776 allocator.try_commit_window_extension(9_999, Epoch(4)),
777 Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
778 expected: Epoch(4),
779 current: Epoch(5),
780 }))
781 );
782 allocator.try_grant(900, 1).unwrap();
785 assert_eq!(
786 allocator.try_grant(1_100, 1),
787 Err(CoreError::WindowExhausted)
788 );
789 }
790
791 #[test]
792 fn commit_after_leadership_lost_is_ignored() {
793 let mut allocator = Allocator::new();
794 allocator
795 .try_on_leadership_gained(1000, 5000, Epoch(1))
796 .unwrap();
797 allocator.on_leadership_lost();
798 assert_eq!(
799 allocator.try_commit_window_extension(9_999, Epoch(1)),
800 Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader))
801 );
802 assert!(!allocator.is_leader());
803 }
804
805 #[test]
806 fn would_grant_matches_try_grant_outcome() {
807 let mut allocator = Allocator::new();
808 assert!(!allocator.would_grant(1_000, 1));
810 allocator
812 .try_on_leadership_gained(1_000, 5_000, Epoch(1))
813 .unwrap();
814 assert!(!allocator.would_grant(1_000, 0));
815 assert!(!allocator.would_grant(1_000, LOGICAL_MAX + 2));
816 assert!(allocator.would_grant(0, 1));
819 assert!(!allocator.would_grant(5_001, 1));
821 assert!(allocator.would_grant(2_500, 1));
823 }
824
825 #[test]
826 fn would_grant_predicts_logical_wrap_advance() {
827 let mut allocator = Allocator::new();
831 allocator
832 .try_on_leadership_gained(1_000, 1_000, Epoch(1))
833 .unwrap();
834 allocator.try_grant(1_000, LOGICAL_MAX + 1).unwrap();
839 assert!(!allocator.would_grant(1_000, 1));
842 }
843
844 #[test]
845 fn would_grant_returns_false_when_advance_exceeds_physical_max() {
846 let mut allocator = Allocator::new();
849 allocator
850 .try_on_leadership_gained(PHYSICAL_MS_MAX, PHYSICAL_MS_MAX, Epoch(1))
851 .unwrap();
852 allocator
855 .try_grant(PHYSICAL_MS_MAX, LOGICAL_MAX + 1)
856 .unwrap();
857 assert!(!allocator.would_grant(PHYSICAL_MS_MAX, 1));
858 }
859
860 #[test]
861 fn default_constructs_not_leader_allocator() {
862 let allocator = Allocator::default();
863 assert!(!allocator.is_leader());
864 assert_eq!(allocator.epoch(), None);
865 }
866
867 #[test]
868 fn logical_wraps_to_next_physical_ms() {
869 let mut allocator = Allocator::new();
870 allocator.try_on_leadership_gained(0, 0, Epoch(1)).unwrap();
872 allocator.try_commit_window_extension(10, Epoch(1)).unwrap();
873 let first = allocator.try_grant(1, LOGICAL_MAX + 1).unwrap();
875 assert_eq!(first.physical_ms, 1);
876 assert_eq!(first.logical_start, 0);
877 let second = allocator.try_grant(1, 1).unwrap();
878 assert_eq!(second.physical_ms, 2);
879 assert_eq!(second.logical_start, 0);
880 }
881
882 #[test]
883 fn try_new_accepts_valid_grant_and_packs_boundaries() {
884 let grant = WindowGrant::try_new(1_000, 5, 3, Epoch(7)).unwrap();
888 assert_eq!(grant.physical_ms(), 1_000);
889 assert_eq!(grant.logical_start(), 5);
890 assert_eq!(grant.count(), 3);
891 assert_eq!(grant.epoch(), Epoch(7));
892 assert_eq!(grant.first(), Timestamp::pack(1_000, 5));
893 assert_eq!(grant.last(), Timestamp::pack(1_000, 7));
894 }
895
896 #[test]
897 fn try_new_accepts_max_logical_boundary() {
898 let grant = WindowGrant::try_new(1_000, 0, LOGICAL_MAX + 1, Epoch(1)).unwrap();
900 assert_eq!(grant.last(), Timestamp::pack(1_000, LOGICAL_MAX));
901 }
902
903 #[test]
904 fn try_new_rejects_zero_count() {
905 assert_eq!(
907 WindowGrant::try_new(1_000, 0, 0, Epoch(1)),
908 Err(CoreError::InvalidCount(0))
909 );
910 }
911
912 #[test]
913 fn try_new_rejects_out_of_range_physical_ms() {
914 assert_eq!(
915 WindowGrant::try_new(PHYSICAL_MS_MAX + 1, 0, 1, Epoch(1)),
916 Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
917 );
918 }
919
920 #[test]
921 fn try_new_rejects_logical_range_overflow() {
922 assert_eq!(
924 WindowGrant::try_new(1_000, LOGICAL_MAX, 2, Epoch(1)),
925 Err(CoreError::LogicalRangeOutOfRange {
926 logical_start: LOGICAL_MAX,
927 count: 2
928 })
929 );
930 }
931
932 #[test]
933 fn try_new_rejects_logical_count_u32_overflow() {
934 assert_eq!(
936 WindowGrant::try_new(1_000, u32::MAX, 2, Epoch(1)),
937 Err(CoreError::LogicalRangeOutOfRange {
938 logical_start: u32::MAX,
939 count: 2
940 })
941 );
942 }
943}