aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--fst/id.go48
-rw-r--r--fst/id_test.go63
-rw-r--r--fst/shared.go16
3 files changed, 111 insertions, 16 deletions
diff --git a/fst/id.go b/fst/id.go
new file mode 100644
index 00000000..a8363c25
--- /dev/null
+++ b/fst/id.go
@@ -0,0 +1,48 @@
+package fst
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+)
+
+type ID [16]byte
+
+var (
+ ErrInvalidLength = errors.New("string representation must have a length of 32")
+)
+
+func (a *ID) String() string {
+ return hex.EncodeToString(a[:])
+}
+
+func NewAppID(id *ID) error {
+ _, err := rand.Read(id[:])
+ return err
+}
+
+func ParseAppID(id *ID, s string) error {
+ if len(s) != 32 {
+ return ErrInvalidLength
+ }
+
+ for i, b := range s {
+ if b < '0' || b > 'f' {
+ return fmt.Errorf("invalid char %q at byte %d", b, i)
+ }
+
+ v := uint8(b)
+ if v > '9' {
+ v = 10 + v - 'a'
+ } else {
+ v -= '0'
+ }
+ if i%2 == 0 {
+ v <<= 4
+ }
+ id[i/2] += v
+ }
+
+ return nil
+}
diff --git a/fst/id_test.go b/fst/id_test.go
new file mode 100644
index 00000000..bba9af22
--- /dev/null
+++ b/fst/id_test.go
@@ -0,0 +1,63 @@
+package fst_test
+
+import (
+ "errors"
+ "testing"
+
+ "git.ophivana.moe/security/fortify/fst"
+)
+
+func TestParseAppID(t *testing.T) {
+ t.Run("bad length", func(t *testing.T) {
+ if err := fst.ParseAppID(new(fst.ID), "meow"); !errors.Is(err, fst.ErrInvalidLength) {
+ t.Errorf("ParseAppID: error = %v, wantErr = %v", err, fst.ErrInvalidLength)
+ }
+ })
+
+ t.Run("bad byte", func(t *testing.T) {
+ wantErr := "invalid char '\\n' at byte 15"
+ if err := fst.ParseAppID(new(fst.ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
+ t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
+ }
+ })
+
+ t.Run("fuzz 16 iterations", func(t *testing.T) {
+ for i := 0; i < 16; i++ {
+ testParseAppIDWithRandom(t)
+ }
+ })
+}
+
+func FuzzParseAppID(f *testing.F) {
+ for i := 0; i < 16; i++ {
+ id := new(fst.ID)
+ if err := fst.NewAppID(id); err != nil {
+ panic(err.Error())
+ }
+ f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
+ }
+
+ f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
+ testParseAppID(t, &fst.ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
+ })
+}
+
+func testParseAppIDWithRandom(t *testing.T) {
+ id := new(fst.ID)
+ if err := fst.NewAppID(id); err != nil {
+ t.Fatalf("cannot generate app ID: %v", err)
+ }
+ testParseAppID(t, id)
+}
+
+func testParseAppID(t *testing.T, id *fst.ID) {
+ s := id.String()
+ got := new(fst.ID)
+ if err := fst.ParseAppID(got, s); err != nil {
+ t.Fatalf("cannot parse app ID: %v", err)
+ }
+
+ if *got != *id {
+ t.Fatalf("ParseAppID(%#v) = \n%#v, want \n%#v", s, got, id)
+ }
+}
diff --git a/fst/shared.go b/fst/shared.go
index 39544863..ae59b236 100644
--- a/fst/shared.go
+++ b/fst/shared.go
@@ -1,18 +1,2 @@
// Package fst exports shared fortify types.
package fst
-
-import (
- "crypto/rand"
- "encoding/hex"
-)
-
-type ID [16]byte
-
-func (a *ID) String() string {
- return hex.EncodeToString(a[:])
-}
-
-func NewAppID(id *ID) error {
- _, err := rand.Read(id[:])
- return err
-}