From 431b7b1a37dbaf537f29396673a99bc9451e72fd Mon Sep 17 00:00:00 2001
From: Mark Puha
Date: Fri, 6 Jun 2025 19:32:17 +0200
Subject: [PATCH] feat: some generators & parser improvements
---
device/internal/junk-tag/generator.go | 98 +++++++++++++++-
device/internal/junk-tag/generator_test.go | 124 +++++++++++++++++++++
device/internal/junk-tag/parser.go | 58 +++++-----
device/internal/junk-tag/parser_test.go | 11 +-
4 files changed, 253 insertions(+), 38 deletions(-)
create mode 100644 device/internal/junk-tag/generator_test.go
diff --git a/device/internal/junk-tag/generator.go b/device/internal/junk-tag/generator.go
index ca96dee..e664233 100644
--- a/device/internal/junk-tag/generator.go
+++ b/device/internal/junk-tag/generator.go
@@ -1,14 +1,106 @@
package junktag
+import (
+ crand "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ v2 "math/rand/v2"
+)
+
type Generator interface {
- Generate() []byte
+ Generate() ([]byte, error)
}
type newGenerator func(string) (Generator, error)
type BytesGenerator struct {
+ value []byte
}
-func (b *BytesGenerator) Generate() []byte {
- return nil
+func (bg *BytesGenerator) Generate() ([]byte, error) {
+ return bg.value, nil
+}
+
+func newBytesGenerator(param string) (Generator, error) {
+ isNotHex := !strings.HasPrefix(param, "0x") ||
+ !strings.HasPrefix(param, "0x") && !isHexString(param)
+ if isNotHex {
+ return nil, fmt.Errorf("not correct hex: %s", param)
+ }
+
+ hex, err := hexToBytes(param)
+ if err != nil {
+ return nil, fmt.Errorf("hexToBytes: %w", err)
+ }
+
+ return &BytesGenerator{value: hex}, nil
+}
+
+func isHexString(s string) bool {
+ for _, char := range s {
+ if !((char >= '0' && char <= '9') ||
+ (char >= 'a' && char <= 'f') ||
+ (char >= 'A' && char <= 'F')) {
+ return false
+ }
+ }
+ return len(s) > 0
+}
+
+func hexToBytes(hexStr string) ([]byte, error) {
+ hexStr = strings.TrimPrefix(hexStr, "0x")
+ hexStr = strings.TrimPrefix(hexStr, "0X")
+
+ // Ensure even length (pad with leading zero if needed)
+ if len(hexStr)%2 != 0 {
+ hexStr = "0" + hexStr
+ }
+
+ return hex.DecodeString(hexStr)
+}
+
+type RandomPacketGenerator struct {
+ cha8Rand *v2.ChaCha8
+ size int
+}
+
+func (rpg *RandomPacketGenerator) Generate() ([]byte, error) {
+ junk := make([]byte, rpg.size)
+ _, err := rpg.cha8Rand.Read(junk)
+ return junk, err
+}
+
+func newRandomPacketGenerator(param string) (Generator, error) {
+ size, err := strconv.Atoi(param)
+ if err != nil {
+ return nil, fmt.Errorf("randome packet parse int: %w", err)
+ }
+ // TODO: add size check
+
+ buf := make([]byte, 32)
+ _, err = crand.Read(buf)
+ if err != nil {
+ return nil, fmt.Errorf("randome packet crand read: %w", err)
+ }
+
+ return &RandomPacketGenerator{cha8Rand: v2.NewChaCha8([32]byte(buf)), size: size}, nil
+}
+
+type TimestampGenerator struct {
+}
+
+func (tg *TimestampGenerator) Generate() ([]byte, error) {
+ return time.Now().MarshalBinary()
+}
+
+func newTimestampGenerator(param string) (Generator, error) {
+ if len(param) != 0 {
+ return nil, fmt.Errorf("timestamp param needs to be empty: %s", param)
+ }
+
+ return &TimestampGenerator{}, nil
}
diff --git a/device/internal/junk-tag/generator_test.go b/device/internal/junk-tag/generator_test.go
new file mode 100644
index 0000000..44bf8a1
--- /dev/null
+++ b/device/internal/junk-tag/generator_test.go
@@ -0,0 +1,124 @@
+package junktag
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_newBytesGenerator(t *testing.T) {
+ type args struct {
+ param string
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ wantErr error
+ }{
+ {
+ name: "empty",
+ args: args{
+ param: "",
+ },
+ wantErr: fmt.Errorf("not correct hex"),
+ },
+ {
+ name: "wrong start",
+ args: args{
+ param: "123456",
+ },
+ wantErr: fmt.Errorf("not correct hex"),
+ },
+ {
+ name: "not only hex value",
+ args: args{
+ param: "0x12345q",
+ },
+ wantErr: fmt.Errorf("not correct hex"),
+ },
+ {
+ name: "valid hex",
+ args: args{
+ param: "0xf6ab3267fa",
+ },
+ want: []byte{0xf6, 0xab, 0x32, 0x67, 0xfa},
+ },
+ {
+ name: "valid hex with odd length",
+ args: args{
+ param: "0xfab3267fa",
+ },
+ want: []byte{0xf, 0xab, 0x32, 0x67, 0xfa},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := newBytesGenerator(tt.args.param)
+
+ if tt.wantErr != nil {
+ require.ErrorAs(t, err, &tt.wantErr)
+ require.Nil(t, got)
+ return
+ }
+
+ require.Nil(t, err)
+ require.NotNil(t, got)
+
+ gotValues, _ := got.Generate()
+ require.Equal(t, tt.want, gotValues)
+ })
+ }
+}
+
+func Test_newRandomPacketGenerator(t *testing.T) {
+ type args struct {
+ param string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr error
+ }{
+ {
+ name: "empty",
+ args: args{
+ param: "",
+ },
+ wantErr: fmt.Errorf("parse int"),
+ },
+ {
+ name: "not an int",
+ args: args{
+ param: "x",
+ },
+ wantErr: fmt.Errorf("parse int"),
+ },
+ {
+ name: "valid",
+ args: args{
+ param: "12",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := newRandomPacketGenerator(tt.args.param)
+ if tt.wantErr != nil {
+ require.ErrorAs(t, err, &tt.wantErr)
+ require.Nil(t, got)
+ return
+ }
+
+ require.Nil(t, err)
+ require.NotNil(t, got)
+ first, err := got.Generate()
+ require.Nil(t, err)
+
+ second, err := got.Generate()
+ require.Nil(t, err)
+ require.NotEqual(t, first, second)
+ })
+ }
+}
diff --git a/device/internal/junk-tag/parser.go b/device/internal/junk-tag/parser.go
index 0160a4e..1d20f85 100644
--- a/device/internal/junk-tag/parser.go
+++ b/device/internal/junk-tag/parser.go
@@ -18,10 +18,10 @@ const (
)
var validEnum = map[Enum]newGenerator{
- EnumBytes: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
+ EnumBytes: newBytesGenerator,
EnumCounter: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
- EnumTimestamp: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
- EnumRandomBytes: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
+ EnumTimestamp: newTimestampGenerator,
+ EnumRandomBytes: newRandomPacketGenerator,
EnumWaitTimeout: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
EnumWaitResponse: func(s string) (Generator, error) { return &BytesGenerator{}, nil },
}
@@ -35,24 +35,19 @@ type Tag struct {
Param string
}
-func parseTags(input string) ([]Tag, error) {
+func parseTag(input string) (Tag, error) {
// Regular expression to match
re := regexp.MustCompile(`([a-zA-Z]+)(?:\s+([^>]+))?>`)
- matches := re.FindAllStringSubmatch(input, -1)
- tags := make([]Tag, 0, len(matches))
-
- for _, match := range matches {
- tag := Tag{
- Name: Enum(match[1]),
- }
- if len(match) > 2 && match[2] != "" {
- tag.Param = strings.TrimSpace(match[2])
- }
- tags = append(tags, tag)
+ match := re.FindStringSubmatch(input)
+ tag := Tag{
+ Name: Enum(match[1]),
+ }
+ if len(match) > 2 && match[2] != "" {
+ tag.Param = strings.TrimSpace(match[2])
}
- return tags, nil
+ return tag, nil
}
func Parse(input string) (Foo, error) {
@@ -62,26 +57,29 @@ func Parse(input string) (Foo, error) {
return Foo{}, fmt.Errorf("empty input: %s", input)
}
- for _, inputParam := range inputSlice[1:] {
- if len(inputParam) == 1 {
+ // skip byproduct of split
+ inputSlice = inputSlice[1:]
+ rv := Foo{x: make([]Generator, 0, len(inputSlice))}
+
+ for _, inputParam := range inputSlice {
+ if len(inputParam) <= 1 {
return Foo{}, fmt.Errorf("empty tag in input: %s", inputSlice)
} else if strings.Count(inputParam, ">") != 1 {
return Foo{}, fmt.Errorf("ill formated input: %s", input)
}
- tags, _ := parseTags(inputParam)
- for _, tag := range tags {
- fmt.Printf("Tag: %s, Param: %s\n", tag.Name, tag.Param)
- gen, ok := validEnum[tag.Name]
- if !ok {
- return Foo{}, fmt.Errorf("invalid tag")
- }
- _, err := gen(tag.Param)
- if err != nil {
- return Foo{}, fmt.Errorf("")
- }
+ tag, _ := parseTag(inputParam)
+ fmt.Printf("Tag: %s, Param: %s\n", tag.Name, tag.Param)
+ gen, ok := validEnum[tag.Name]
+ if !ok {
+ return Foo{}, fmt.Errorf("invalid tag: %s", tag.Name)
}
+ generator, err := gen(tag.Param)
+ if err != nil {
+ return Foo{}, fmt.Errorf("gen: %w", err)
+ }
+ rv.x = append(rv.x, generator)
}
- return Foo{}, nil
+ return rv, nil
}
diff --git a/device/internal/junk-tag/parser_test.go b/device/internal/junk-tag/parser_test.go
index 6c1f33b..d9b9642 100644
--- a/device/internal/junk-tag/parser_test.go
+++ b/device/internal/junk-tag/parser_test.go
@@ -29,7 +29,7 @@ func TestParse(t *testing.T) {
{
name: "extra <",
args: args{input: "<"},
- wantErr: fmt.Errorf("ill formated input"),
+ wantErr: fmt.Errorf("empty tag in input"),
},
{
name: "empty <>",
@@ -51,10 +51,11 @@ func TestParse(t *testing.T) {
_, err := Parse(tt.args.input)
// TODO: ErrorAs doesn't work as you think
- // if tt.wantErr != nil {
- require.ErrorAs(t, err, &tt.wantErr)
- // return
- // }
+ if tt.wantErr != nil {
+ require.ErrorAs(t, err, &tt.wantErr)
+ return
+ }
+ require.Nil(t, err)
})
}
}