// Copyright (c) 2026 Micha Hoiting package serial import ( "crypto/rand" "encoding/binary" "errors" "fmt" ) const ( MaxIndex = block MaxRandomAttempts = 1024 letters = 26 numbers = 900 // 100–999 block = letters * letters * letters * letters * numbers // 26^4 * 900 ) var ( ErrInvalidFormat = errors.New("invalid format, expected LLL-NNN-LC") ErrInvalidLetters = errors.New("invalid letters") ErrForbiddenLetterTriplet = errors.New("forbidden letter combination") ErrInvalidNumber = errors.New("invalid number") ErrNumberOutOfRange = errors.New("number out of range") ErrInvalidChecksum = errors.New("invalid checksum") ErrIndexOutOfRange = errors.New("index out of range") ErrRandomSourceFailed = errors.New("random source failed") ErrNoAvailableIndex = errors.New("no available index found") ) type RandomCodeOptions struct { MaxAttempts int IsInUse func(uint32) bool RandomIndex func(max uint32) (uint32, error) } func Encode(code string) (uint32, error) { // Expected format: L1L2L3-NNN-L4C => length 10, positions: 0,1,2,4,5,6,8,9 if len(code) != 10 || code[3] != '-' || code[7] != '-' { return 0, ErrInvalidFormat } l1, l2, l3, l4 := code[0], code[1], code[2], code[8] n1, n2, n3 := code[4], code[5], code[6] // Validate letters // Forbidden rule applies to l1,l2,l3 // Parse number from code[4:7] if !isUpperAlphaASCII(l1) || !isUpperAlphaASCII(l2) || !isUpperAlphaASCII(l3) || !isUpperAlphaASCII(l4) { return 0, ErrInvalidLetters } if IsForbiddenTriplet(l1, l2, l3) { return 0, ErrForbiddenLetterTriplet } if !isNumber(n1) || !isNumber(n2) || !isNumber(n3) { return 0, ErrInvalidNumber } L1 := uint32(l1 - 'A') L2 := uint32(l2 - 'A') L3 := uint32(l3 - 'A') L4 := uint32(l4 - 'A') num := uint32(n1-'0')*100 + uint32(n2-'0')*10 + uint32(n3-'0') if num < 100 || num > 999 { return 0, ErrNumberOutOfRange } N := num - 100 idx := uint32((((((L1*letters+L2)*letters+L3)*letters + L4) * numbers) + N)) if code[9] != checksumLetter(idx) { return 0, ErrInvalidChecksum } return idx, nil } func Decode(idx uint32) (string, error) { if idx >= MaxIndex { return "", ErrIndexOutOfRange } x := int(idx) L1 := x / (letters * letters * letters * numbers) r1 := x % (letters * letters * letters * numbers) L2 := r1 / (letters * letters * numbers) r2 := r1 % (letters * letters * numbers) L3 := r2 / (letters * numbers) r3 := r2 % (letters * numbers) L4 := r3 / numbers N := r3 % numbers l1 := byte('A' + L1) l2 := byte('A' + L2) l3 := byte('A' + L3) l4 := byte('A' + L4) if IsForbiddenTriplet(l1, l2, l3) { return "", ErrForbiddenLetterTriplet } c := checksumLetter(idx) code := fmt.Sprintf("%c%c%c-%03d-%c%c", l1, l2, l3, N+100, l4, c) return code, nil } func RandomCode(isInUse ...func(uint32) bool) (string, uint32, error) { options := RandomCodeOptions{} if len(isInUse) > 0 { options.IsInUse = isInUse[0] } return RandomCodeWithOptions(options) } func RandomCodeWithOptions(options RandomCodeOptions) (string, uint32, error) { maxAttempts := options.MaxAttempts if maxAttempts <= 0 { maxAttempts = MaxRandomAttempts } inUse := options.IsInUse randomIndex := options.RandomIndex if randomIndex == nil { randomIndex = randomUint32n } for attempts := 0; attempts < maxAttempts; attempts++ { idx, err := randomIndex(MaxIndex) if err != nil { return "", 0, fmt.Errorf("%w: %v", ErrRandomSourceFailed, err) } if idx >= MaxIndex { return "", 0, fmt.Errorf("%w: index out of range", ErrRandomSourceFailed) } if inUse != nil && inUse(idx) { continue } code, err := Decode(idx) if err == nil { return code, idx, nil } if errors.Is(err, ErrForbiddenLetterTriplet) { continue } return "", 0, err } return "", 0, ErrNoAvailableIndex } func checksumLetter(idx uint32) byte { return byte('A' + idx%26) } func isUpperAlphaASCII(b byte) bool { return (b >= 'A' && b <= 'Z') } func isNumber(b byte) bool { return b >= '0' && b <= '9' } func randomUint32n(n uint32) (uint32, error) { if n == 0 { return 0, ErrIndexOutOfRange } maxUint32 := ^uint32(0) limit := maxUint32 - (maxUint32 % n) for { var buf [4]byte if _, err := rand.Read(buf[:]); err != nil { return 0, err } v := binary.LittleEndian.Uint32(buf[:]) if v < limit { return v % n, nil } } } func IsForbiddenTriplet(l1, l2, l3 byte) bool { return false }