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 }