Files
lafsession/replay.go
wuka bb1e3cf140 33
2026-09-23 16:09:17 +02:00

86 lines
1.6 KiB
Go

package lafsession
// replayWindow is a sliding bit-window anti-replay tracker for a single direction.
type replayWindow struct {
maxSeq uint64
size uint64 // bits
bits []uint64
}
func newReplayWindow(size uint64) *replayWindow {
if size < 64 {
size = 64
}
words := (size + 63) / 64
return &replayWindow{
size: size,
bits: make([]uint64, words),
}
}
// accept returns true if seq is new and within window; marks it seen.
func (w *replayWindow) accept(seq uint64) bool {
if seq+w.size <= w.maxSeq {
// too old
return false
}
if seq > w.maxSeq {
shift := seq - w.maxSeq
if shift >= w.size {
// clear entire window
for i := range w.bits {
w.bits[i] = 0
}
} else {
w.shift(shift)
}
w.maxSeq = seq
}
idx := w.maxSeq - seq
word := idx / 64
bit := idx % 64
if word >= uint64(len(w.bits)) {
return false
}
mask := uint64(1) << bit
if w.bits[word]&mask != 0 {
return false // already seen
}
w.bits[word] |= mask
return true
}
func (w *replayWindow) shift(n uint64) {
words := n / 64
bits := n % 64
if words > 0 {
copy(w.bits[words:], w.bits)
for i := uint64(0); i < words && i < uint64(len(w.bits)); i++ {
w.bits[i] = 0
}
// zero the tail that was shifted out conceptually
for i := uint64(len(w.bits)); i > words; i-- {
// already copied
}
if words < uint64(len(w.bits)) {
for i := 0; i < int(words); i++ {
w.bits[i] = 0
}
} else {
for i := range w.bits {
w.bits[i] = 0
}
return
}
}
if bits == 0 {
return
}
var carry uint64
for i := len(w.bits) - 1; i >= 0; i-- {
newCarry := w.bits[i] >> (64 - bits)
w.bits[i] = (w.bits[i] << bits) | carry
carry = newCarry
}
}