feat: add command domain invariants
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// Package domain contains transport- and storage-independent RVBox rules.
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
googleuuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidUUIDv7 = errors.New("invalid canonical UUIDv7")
|
||||
ErrUUIDTimeRange = errors.New("UUIDv7 time is outside its 48-bit range")
|
||||
)
|
||||
|
||||
const maxUUIDv7UnixMilli = uint64(1<<48 - 1)
|
||||
|
||||
// UUID is the canonical binary form used by RVBox domain and storage code.
|
||||
type UUID [16]byte
|
||||
|
||||
// String returns the lowercase RFC 9562 canonical representation.
|
||||
func (u UUID) String() string {
|
||||
return googleuuid.UUID(u).String()
|
||||
}
|
||||
|
||||
// ParseUUIDv7 accepts only the lowercase, hyphenated RFC 9562 form.
|
||||
func ParseUUIDv7(value string) (UUID, error) {
|
||||
if len(value) != 36 {
|
||||
return UUID{}, fmt.Errorf("%w: expected 36 characters", ErrInvalidUUIDv7)
|
||||
}
|
||||
|
||||
parsed, err := googleuuid.Parse(value)
|
||||
if err != nil || parsed.String() != value {
|
||||
return UUID{}, fmt.Errorf("%w: non-canonical encoding", ErrInvalidUUIDv7)
|
||||
}
|
||||
if parsed.Version() != 7 || parsed.Variant() != googleuuid.RFC4122 {
|
||||
return UUID{}, fmt.Errorf("%w: expected RFC variant and version 7", ErrInvalidUUIDv7)
|
||||
}
|
||||
|
||||
return UUID(parsed), nil
|
||||
}
|
||||
|
||||
// UUIDv7Generator emits lexically increasing UUIDv7 values even when the wall
|
||||
// clock stalls or moves backwards. Calls are safe from multiple goroutines.
|
||||
type UUIDv7Generator struct {
|
||||
mu sync.Mutex
|
||||
now func() time.Time
|
||||
entropy io.Reader
|
||||
started bool
|
||||
lastMS uint64
|
||||
sequence uint16
|
||||
}
|
||||
|
||||
// NewUUIDv7Generator creates a generator. Nil arguments select the system clock
|
||||
// and crypto/rand.Reader. Supplying dependencies keeps boundary tests hermetic.
|
||||
func NewUUIDv7Generator(now func() time.Time, entropy io.Reader) *UUIDv7Generator {
|
||||
if now == nil {
|
||||
now = time.Now
|
||||
}
|
||||
if entropy == nil {
|
||||
entropy = rand.Reader
|
||||
}
|
||||
return &UUIDv7Generator{now: now, entropy: entropy}
|
||||
}
|
||||
|
||||
var defaultUUIDv7Generator = NewUUIDv7Generator(nil, nil)
|
||||
|
||||
// NewUUIDv7 uses the process-wide generator.
|
||||
func NewUUIDv7() (UUID, error) {
|
||||
return defaultUUIDv7Generator.New()
|
||||
}
|
||||
|
||||
// New returns a canonical RFC 9562 UUIDv7.
|
||||
func (g *UUIDv7Generator) New() (UUID, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
instant := g.now()
|
||||
millis := instant.UnixMilli()
|
||||
if millis < 0 || uint64(millis) > maxUUIDv7UnixMilli {
|
||||
return UUID{}, ErrUUIDTimeRange
|
||||
}
|
||||
|
||||
var random [10]byte
|
||||
if _, err := io.ReadFull(g.entropy, random[:]); err != nil {
|
||||
return UUID{}, fmt.Errorf("read UUIDv7 entropy: %w", err)
|
||||
}
|
||||
|
||||
wallMS := uint64(millis)
|
||||
logicalMS := wallMS
|
||||
sequence := uint16(random[0]&0x0f)<<8 | uint16(random[1])
|
||||
if g.started && wallMS <= g.lastMS {
|
||||
logicalMS = g.lastMS
|
||||
sequence = g.sequence + 1
|
||||
if sequence > 0x0fff {
|
||||
if logicalMS == maxUUIDv7UnixMilli {
|
||||
return UUID{}, ErrUUIDTimeRange
|
||||
}
|
||||
logicalMS++
|
||||
sequence = 0
|
||||
}
|
||||
}
|
||||
|
||||
var result UUID
|
||||
result[0] = byte(logicalMS >> 40)
|
||||
result[1] = byte(logicalMS >> 32)
|
||||
result[2] = byte(logicalMS >> 24)
|
||||
result[3] = byte(logicalMS >> 16)
|
||||
result[4] = byte(logicalMS >> 8)
|
||||
result[5] = byte(logicalMS)
|
||||
result[6] = 0x70 | byte(sequence>>8)
|
||||
result[7] = byte(sequence)
|
||||
result[8] = 0x80 | random[2]&0x3f
|
||||
copy(result[9:], random[3:])
|
||||
|
||||
g.started = true
|
||||
g.lastMS = logicalMS
|
||||
g.sequence = sequence
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user