Initial version
This commit is contained in:
61
serial/serial.go
Normal file
61
serial/serial.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const (
|
||||||
|
letters = 26
|
||||||
|
numbers = 900 // 100–999
|
||||||
|
block = letters * letters * letters * numbers // 26^3 * 900
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encode(code string) (uint32, error) {
|
||||||
|
// Verwacht formaat: L1L2-NNN-L3-C => lengte 10, posities: 0,1,3,4,5,7,9
|
||||||
|
if len(code) != 10 || code[2] != '-' || code[6] != '-' || code[8] != '-' {
|
||||||
|
return 0, errors.New("invalid format, expected LL-NNN-L-C")
|
||||||
|
}
|
||||||
|
|
||||||
|
l1, l2, l3 := code[0], code[1], code[7]
|
||||||
|
|
||||||
|
// letters checken
|
||||||
|
// forbidden alleen op l1,l2
|
||||||
|
// nummer parsen uit code[3:6]
|
||||||
|
|
||||||
|
L1 := int(l1 - 'A')
|
||||||
|
L2 := int(l2 - 'A')
|
||||||
|
L3 := int(l3 - 'A')
|
||||||
|
N := num - 100
|
||||||
|
|
||||||
|
idx := uint32((((L1*letters + L2)*letters + L3) * numbers) + N)
|
||||||
|
|
||||||
|
if code[9] != checksumLetter(idx) {
|
||||||
|
return 0, errors.New("invalid checksum")
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(idx uint32) (string, error) {
|
||||||
|
if idx >= block {
|
||||||
|
return "", errors.New("index out of range")
|
||||||
|
}
|
||||||
|
|
||||||
|
x := int(idx)
|
||||||
|
|
||||||
|
L1 := x / (letters * letters * numbers)
|
||||||
|
r1 := x % (letters * letters * numbers)
|
||||||
|
|
||||||
|
L2 := r1 / (letters * numbers)
|
||||||
|
r2 := r1 % (letters * numbers)
|
||||||
|
|
||||||
|
L3 := r2 / numbers
|
||||||
|
N := r2 % numbers
|
||||||
|
|
||||||
|
l1 := byte('A' + L1)
|
||||||
|
l2 := byte('A' + L2)
|
||||||
|
l3 := byte('A' + L3)
|
||||||
|
|
||||||
|
if isForbiddenPair(l1, l2) {
|
||||||
|
return "", errors.New("forbidden letter combination")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := checksumLetter(idx)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%c%c-%03d-%c-%c", l1, l2, N+100, l3, c), nil
|
||||||
|
}
|
||||||
|
|
||||||
128
serial/serial_test.go
Normal file
128
serial/serial_test.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package serial_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"yourmodule/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Property: Decode(Encode(code)) == code (voor geldige codes)
|
||||||
|
func TestEncodeDecodeRoundtrip(t *testing.T) {
|
||||||
|
f := func(idx uint32) bool {
|
||||||
|
if idx >= serial.MaxIndex {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := serial.Decode(idx)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
idx2, err := serial.Encode(code)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx == idx2
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatalf("Encode/Decode roundtrip failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property: Decode(Encode(Decode(i))) == Decode(i) (idempotent op geldige indices)
|
||||||
|
func TestDecodeEncodeDecodeIdempotent(t *testing.T) {
|
||||||
|
f := func(idx uint32) bool {
|
||||||
|
if idx >= serial.MaxIndex {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
code1, err := serial.Decode(idx)
|
||||||
|
if err != nil {
|
||||||
|
return true // decode mag indices weigeren (bv. forbidden pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx2, err := serial.Encode(code1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
code2, err := serial.Decode(idx2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return code1 == code2
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatalf("Decode/Encode/Decode idempotence failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property: forbidden eerste twee letters komen nooit uit Decode
|
||||||
|
func TestForbiddenPairsNeverDecoded(t *testing.T) {
|
||||||
|
f := func(idx uint32) bool {
|
||||||
|
if idx >= serial.MaxIndex {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := serial.Decode(idx)
|
||||||
|
if err != nil {
|
||||||
|
return true // decode mag indices weigeren
|
||||||
|
}
|
||||||
|
|
||||||
|
l1 := code[0]
|
||||||
|
l2 := code[1]
|
||||||
|
|
||||||
|
return !serial.IsForbiddenPair(l1, l2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatalf("Forbidden pairs appeared in Decode: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: basis-check op formaat LL-NNN-L-C
|
||||||
|
func TestDecodedFormatLooksCorrect(t *testing.T) {
|
||||||
|
f := func(idx uint32) bool {
|
||||||
|
if idx >= serial.MaxIndex {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := serial.Decode(idx)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(code) != 10 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if code[2] != '-' || code[6] != '-' || code[8] != '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// letters op posities 0,1,7,9
|
||||||
|
for _, p := range []int{0, 1, 7, 9} {
|
||||||
|
if code[p] < 'A' || code[p] > 'Z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cijfers op posities 3,4,5
|
||||||
|
for _, p := range []int{3, 4, 5} {
|
||||||
|
if code[p] < '0' || code[p] > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatalf("Decoded format property failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user