@@ -16,9 +16,24 @@ impl ReplayWindow {
1616 Self :: default ( )
1717 }
1818
19- /// Check if the given sequence number is acceptable and update the window state.
20- /// Returns true if fresh/acceptable, false if duplicate/too old.
21- pub fn check_and_update ( & mut self , seqno : u64 ) -> bool {
19+ /// Check if the given sequence number is acceptable (not a replay, not too old).
20+ /// Read-only: does not modify the window state.
21+ pub fn check ( & self , seqno : u64 ) -> bool {
22+ if seqno > self . max_seq {
23+ true
24+ } else {
25+ let offset = self . max_seq - seqno;
26+ if offset >= 64 {
27+ return false ; // too old
28+ }
29+ let mask = 1u64 << offset;
30+ ( self . window & mask) == 0 // false if duplicate
31+ }
32+ }
33+
34+ /// Update the window state to record that `seqno` has been received.
35+ /// Must only be called after the record has been authenticated (decrypted successfully).
36+ pub fn update ( & mut self , seqno : u64 ) {
2237 if seqno > self . max_seq {
2338 let delta = seqno - self . max_seq ;
2439 if delta > 63 {
@@ -29,18 +44,11 @@ impl ReplayWindow {
2944 self . window |= 1 ; // mark newest as seen
3045 }
3146 self . max_seq = seqno;
32- true
3347 } else {
3448 let offset = self . max_seq - seqno;
35- if offset >= 64 {
36- return false ; // too old
37- }
38- let mask = 1u64 << offset;
39- if ( self . window & mask) != 0 {
40- return false ; // duplicate
49+ if offset < 64 {
50+ self . window |= 1u64 << offset;
4151 }
42- self . window |= mask;
43- true
4452 }
4553 }
4654}
@@ -49,54 +57,87 @@ impl ReplayWindow {
4957mod tests {
5058 use super :: * ;
5159
60+ /// Helper: check and update in one step (simulates authenticated record).
61+ fn check_and_update ( w : & mut ReplayWindow , seqno : u64 ) -> bool {
62+ if w. check ( seqno) {
63+ w. update ( seqno) ;
64+ true
65+ } else {
66+ false
67+ }
68+ }
69+
5270 #[ test]
5371 fn accepts_fresh_and_rejects_duplicate ( ) {
5472 let mut w = ReplayWindow :: new ( ) ;
55- assert ! ( w . check_and_update( 1 ) ) ;
56- assert ! ( !w . check_and_update( 1 ) ) ; // duplicate
57- assert ! ( w . check_and_update( 2 ) ) ; // next fresh
73+ assert ! ( check_and_update( & mut w , 1 ) ) ;
74+ assert ! ( !check_and_update( & mut w , 1 ) ) ; // duplicate
75+ assert ! ( check_and_update( & mut w , 2 ) ) ; // next fresh
5876 }
5977
6078 #[ test]
6179 fn accepts_out_of_order_within_window ( ) {
6280 let mut w = ReplayWindow :: new ( ) ;
63- assert ! ( w . check_and_update( 10 ) ) ; // establish max=10
64- assert ! ( w . check_and_update( 8 ) ) ; // unseen within 64
65- assert ! ( !w . check_and_update( 8 ) ) ; // duplicate now
66- assert ! ( w . check_and_update( 9 ) ) ; // unseen within 64
81+ assert ! ( check_and_update( & mut w , 10 ) ) ; // establish max=10
82+ assert ! ( check_and_update( & mut w , 8 ) ) ; // unseen within 64
83+ assert ! ( !check_and_update( & mut w , 8 ) ) ; // duplicate now
84+ assert ! ( check_and_update( & mut w , 9 ) ) ; // unseen within 64
6785 }
6886
6987 #[ test]
7088 fn rejects_too_old ( ) {
7189 let mut w = ReplayWindow :: new ( ) ;
72- assert ! ( w . check_and_update( 100 ) ) ;
90+ assert ! ( check_and_update( & mut w , 100 ) ) ;
7391 // offset = 64 -> too old
74- assert ! ( !w . check_and_update( 36 ) ) ;
92+ assert ! ( !check_and_update( & mut w , 36 ) ) ;
7593 // offset = 63 -> allowed once
76- assert ! ( w . check_and_update( 37 ) ) ;
94+ assert ! ( check_and_update( & mut w , 37 ) ) ;
7795 }
7896
7997 #[ test]
8098 fn handles_large_jump_and_window_shift ( ) {
8199 let mut w = ReplayWindow :: new ( ) ;
82- assert ! ( w . check_and_update( 1 ) ) ;
100+ assert ! ( check_and_update( & mut w , 1 ) ) ;
83101 // Large forward jump clears the window entirely
84- assert ! ( w . check_and_update( 80 ) ) ;
102+ assert ! ( check_and_update( & mut w , 80 ) ) ;
85103 // Within window of new max and unseen
86- assert ! ( w . check_and_update( 79 ) ) ;
104+ assert ! ( check_and_update( & mut w , 79 ) ) ;
87105 // Too old relative to new max
88- assert ! ( !w . check_and_update( 15 ) ) ;
106+ assert ! ( !check_and_update( & mut w , 15 ) ) ;
89107 }
90108
91109 #[ test]
92110 fn large_jump_does_not_leave_stale_bits ( ) {
93111 let mut w = ReplayWindow :: new ( ) ;
94- assert ! ( w . check_and_update( 0 ) ) ;
112+ assert ! ( check_and_update( & mut w , 0 ) ) ;
95113 // Jump of 200 exceeds window size (64). The window must be fully
96114 // cleared so no stale bits from seq 0 remain.
97- assert ! ( w . check_and_update( 200 ) ) ;
115+ assert ! ( check_and_update( & mut w , 200 ) ) ;
98116 // seq 137 is within the window (offset = 200 - 137 = 63) and was
99117 // never seen, so it must be accepted.
100- assert ! ( w. check_and_update( 137 ) ) ;
118+ assert ! ( check_and_update( & mut w, 137 ) ) ;
119+ }
120+
121+ #[ test]
122+ fn check_does_not_modify_window ( ) {
123+ let mut w = ReplayWindow :: new ( ) ;
124+ w. update ( 10 ) ;
125+ // check alone should not change state
126+ assert ! ( w. check( 11 ) ) ;
127+ assert ! ( w. check( 11 ) ) ; // still acceptable because update was never called
128+ w. update ( 11 ) ;
129+ assert ! ( !w. check( 11 ) ) ; // now it's a duplicate
130+ }
131+
132+ #[ test]
133+ fn failed_auth_does_not_advance_window ( ) {
134+ let mut w = ReplayWindow :: new ( ) ;
135+ w. update ( 5 ) ;
136+ // Simulate receiving seq 200 that passes check but fails authentication
137+ assert ! ( w. check( 200 ) ) ;
138+ // Do NOT call update (authentication failed)
139+ // Legitimate packet at seq 6 should still be accepted
140+ assert ! ( w. check( 6 ) ) ;
141+ w. update ( 6 ) ;
101142 }
102143}
0 commit comments