68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package domain
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
|
|
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidEventSequence = errors.New("event sequence must be nonzero")
|
|
ErrEventSequenceGap = errors.New("event sequence gap")
|
|
ErrConflictingDuplicate = errors.New("conflicting duplicate event")
|
|
ErrMissingDuplicate = errors.New("duplicate event has no durable fingerprint")
|
|
ErrInvalidTruncationRange = errors.New("invalid output truncation range")
|
|
)
|
|
|
|
type EventFingerprint [sha256.Size]byte
|
|
|
|
func FingerprintBytes(value []byte) EventFingerprint { return sha256.Sum256(value) }
|
|
|
|
type EventSequenceDecision uint8
|
|
|
|
const (
|
|
EventSequenceInvalid EventSequenceDecision = iota
|
|
EventSequenceAppend
|
|
EventSequenceDuplicate
|
|
)
|
|
|
|
// ClassifyEventSequence admits exactly the next event, or an immutable replay
|
|
// whose stored fingerprint is supplied. Wire event-sequence gaps are never
|
|
// repaired by inventing events.
|
|
func ClassifyEventSequence(last, incoming uint64, stored *EventFingerprint, candidate EventFingerprint) (EventSequenceDecision, error) {
|
|
if incoming == 0 {
|
|
return EventSequenceInvalid, ErrInvalidEventSequence
|
|
}
|
|
if last != ^uint64(0) && incoming == last+1 {
|
|
return EventSequenceAppend, nil
|
|
}
|
|
if incoming > last {
|
|
return EventSequenceInvalid, fmt.Errorf("%w: have %d, received %d", ErrEventSequenceGap, last, incoming)
|
|
}
|
|
if stored == nil {
|
|
return EventSequenceInvalid, ErrMissingDuplicate
|
|
}
|
|
if *stored != candidate {
|
|
return EventSequenceInvalid, ErrConflictingDuplicate
|
|
}
|
|
return EventSequenceDuplicate, nil
|
|
}
|
|
|
|
// ValidateOutputTruncation checks the two allowed forms: both range endpoints
|
|
// absent for pre-sequencing client loss, or a complete nonempty retained range.
|
|
func ValidateOutputTruncation(value *rvboxv1.OutputTruncation) error {
|
|
if value == nil {
|
|
return ErrInvalidTruncationRange
|
|
}
|
|
first, last := value.FirstRemovedEventSeq, value.LastRemovedEventSeq
|
|
if first == nil && last == nil {
|
|
return nil
|
|
}
|
|
if first == nil || last == nil || *first == 0 || *last == 0 || *first > *last {
|
|
return ErrInvalidTruncationRange
|
|
}
|
|
return nil
|
|
}
|