Ready for testing.

This commit is contained in:
2026-02-20 22:59:44 +01:00
parent 027307383e
commit 82679048e0
3 changed files with 153 additions and 126 deletions

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.hoiting.org/micha/triplex
go 1.25.0

View File

@@ -1,61 +1,86 @@
package serial
import (
"errors"
"fmt"
"log"
)
const ( const (
letters = 26 MaxIndex = block
numbers = 900 // 100999
block = letters * letters * letters * numbers // 26^3 * 900 letters = 26
numbers = 900 // 100999
block = letters * letters * letters * numbers // 26^3 * 900
) )
func Encode(code string) (uint32, error) { func Encode(code string) (uint32, error) {
// Verwacht formaat: L1L2-NNN-L3-C => lengte 10, posities: 0,1,3,4,5,7,9 log.Printf("code: %v", code)
if len(code) != 10 || code[2] != '-' || code[6] != '-' || code[8] != '-' { // Verwacht formaat: L1L2-NNN-L3-C => lengte 10, posities: 0,1,3,4,5,7,9
return 0, errors.New("invalid format, expected LL-NNN-L-C") 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] l1, l2, l3 := code[0], code[1], code[7]
n1, n2, n3 := code[3], code[4], code[5]
// letters checken // letters checken
// forbidden alleen op l1,l2 // forbidden alleen op l1,l2
// nummer parsen uit code[3:6] // nummer parsen uit code[3:6]
L1 := int(l1 - 'A') L1 := uint32(l1 - 'A')
L2 := int(l2 - 'A') L2 := uint32(l2 - 'A')
L3 := int(l3 - 'A') L3 := uint32(l3 - 'A')
N := num - 100 num := uint32(n1*100 + n2*10 + n3)
N := num - 100
idx := uint32((((L1*letters + L2)*letters + L3) * numbers) + N) idx := uint32((((L1*letters+L2)*letters + L3) * numbers) + N)
if code[9] != checksumLetter(idx) { if code[9] != checksumLetter(idx) {
return 0, errors.New("invalid checksum") return 0, errors.New("invalid checksum")
} }
return idx, nil log.Printf("idx: %v", idx)
return idx, nil
} }
func Decode(idx uint32) (string, error) { func Decode(idx uint32) (string, error) {
if idx >= block { log.Printf("idx: %v", idx)
return "", errors.New("index out of range") if idx >= block {
} return "", errors.New("index out of range")
}
x := int(idx) x := int(idx)
L1 := x / (letters * letters * numbers) L1 := x / (letters * letters * numbers)
r1 := x % (letters * letters * numbers) r1 := x % (letters * letters * numbers)
L2 := r1 / (letters * numbers) L2 := r1 / (letters * numbers)
r2 := r1 % (letters * numbers) r2 := r1 % (letters * numbers)
L3 := r2 / numbers L3 := r2 / numbers
N := r2 % numbers N := r2 % numbers
l1 := byte('A' + L1) l1 := byte('A' + L1)
l2 := byte('A' + L2) l2 := byte('A' + L2)
l3 := byte('A' + L3) l3 := byte('A' + L3)
if isForbiddenPair(l1, l2) { if IsForbiddenPair(l1, l2) {
return "", errors.New("forbidden letter combination") return "", errors.New("forbidden letter combination")
} }
c := checksumLetter(idx) c := checksumLetter(idx)
return fmt.Sprintf("%c%c-%03d-%c-%c", l1, l2, N+100, l3, c), nil code := fmt.Sprintf("%c%c-%03d-%c-%c", l1, l2, N+100, l3, c)
log.Printf("code: %v", code)
return code, nil
} }
func checksumLetter(idx uint32) byte {
return byte('A' + idx%26)
}
func IsForbiddenPair(l1, l2 byte) bool {
return false
}

View File

@@ -1,128 +1,127 @@
package serial_test package serial_test
import ( import (
"testing" "testing"
"testing/quick" "testing/quick"
"yourmodule/serial" "git.hoiting.org/micha/triplex/serial"
) )
// Property: Decode(Encode(code)) == code (voor geldige codes) // Property: Decode(Encode(code)) == code (voor geldige codes)
func TestEncodeDecodeRoundtrip(t *testing.T) { func TestEncodeDecodeRoundtrip(t *testing.T) {
f := func(idx uint32) bool { f := func(idx uint32) bool {
if idx >= serial.MaxIndex { if idx >= serial.MaxIndex {
return true return true
} }
code, err := serial.Decode(idx) code, err := serial.Decode(idx)
if err != nil { if err != nil {
return false return false
} }
idx2, err := serial.Encode(code) idx2, err := serial.Encode(code)
if err != nil { if err != nil {
return false return false
} }
return idx == idx2 return idx == idx2
} }
if err := quick.Check(f, nil); err != nil { if err := quick.Check(f, nil); err != nil {
t.Fatalf("Encode/Decode roundtrip failed: %v", err) t.Fatalf("Encode/Decode roundtrip failed: %v", err)
} }
} }
// Property: Decode(Encode(Decode(i))) == Decode(i) (idempotent op geldige indices) // Property: Decode(Encode(Decode(i))) == Decode(i) (idempotent op geldige indices)
func TestDecodeEncodeDecodeIdempotent(t *testing.T) { func TestDecodeEncodeDecodeIdempotent(t *testing.T) {
f := func(idx uint32) bool { f := func(idx uint32) bool {
if idx >= serial.MaxIndex { if idx >= serial.MaxIndex {
return true return true
} }
code1, err := serial.Decode(idx) code1, err := serial.Decode(idx)
if err != nil { if err != nil {
return true // decode mag indices weigeren (bv. forbidden pair) return true // decode mag indices weigeren (bv. forbidden pair)
} }
idx2, err := serial.Encode(code1) idx2, err := serial.Encode(code1)
if err != nil { if err != nil {
return false return false
} }
code2, err := serial.Decode(idx2) code2, err := serial.Decode(idx2)
if err != nil { if err != nil {
return false return false
} }
return code1 == code2 return code1 == code2
} }
if err := quick.Check(f, nil); err != nil { if err := quick.Check(f, nil); err != nil {
t.Fatalf("Decode/Encode/Decode idempotence failed: %v", err) t.Fatalf("Decode/Encode/Decode idempotence failed: %v", err)
} }
} }
// Property: forbidden eerste twee letters komen nooit uit Decode // Property: forbidden eerste twee letters komen nooit uit Decode
func TestForbiddenPairsNeverDecoded(t *testing.T) { func TestForbiddenPairsNeverDecoded(t *testing.T) {
f := func(idx uint32) bool { f := func(idx uint32) bool {
if idx >= serial.MaxIndex { if idx >= serial.MaxIndex {
return true return true
} }
code, err := serial.Decode(idx) code, err := serial.Decode(idx)
if err != nil { if err != nil {
return true // decode mag indices weigeren return true // decode mag indices weigeren
} }
l1 := code[0] l1 := code[0]
l2 := code[1] l2 := code[1]
return !serial.IsForbiddenPair(l1, l2) return !serial.IsForbiddenPair(l1, l2)
} }
if err := quick.Check(f, nil); err != nil { if err := quick.Check(f, nil); err != nil {
t.Fatalf("Forbidden pairs appeared in Decode: %v", err) t.Fatalf("Forbidden pairs appeared in Decode: %v", err)
} }
} }
// Optional: basis-check op formaat LL-NNN-L-C // Optional: basis-check op formaat LL-NNN-L-C
func TestDecodedFormatLooksCorrect(t *testing.T) { func TestDecodedFormatLooksCorrect(t *testing.T) {
f := func(idx uint32) bool { f := func(idx uint32) bool {
if idx >= serial.MaxIndex { if idx >= serial.MaxIndex {
return true return true
} }
code, err := serial.Decode(idx) code, err := serial.Decode(idx)
if err != nil { if err != nil {
return true return true
} }
if len(code) != 10 { if len(code) != 10 {
return false return false
} }
if code[2] != '-' || code[6] != '-' || code[8] != '-' { if code[2] != '-' || code[6] != '-' || code[8] != '-' {
return false return false
} }
// letters op posities 0,1,7,9 // letters op posities 0,1,7,9
for _, p := range []int{0, 1, 7, 9} { for _, p := range []int{0, 1, 7, 9} {
if code[p] < 'A' || code[p] > 'Z' { if code[p] < 'A' || code[p] > 'Z' {
return false return false
} }
} }
// cijfers op posities 3,4,5 // cijfers op posities 3,4,5
for _, p := range []int{3, 4, 5} { for _, p := range []int{3, 4, 5} {
if code[p] < '0' || code[p] > '9' { if code[p] < '0' || code[p] > '9' {
return false return false
} }
} }
return true return true
} }
if err := quick.Check(f, nil); err != nil { if err := quick.Check(f, nil); err != nil {
t.Fatalf("Decoded format property failed: %v", err) t.Fatalf("Decoded format property failed: %v", err)
} }
} }