Skip to content

Commit

Permalink
chore: remove sring copy in hexToBytes
Browse files Browse the repository at this point in the history
  • Loading branch information
maharifu committed Nov 28, 2024
1 parent 7b86d40 commit 674a72f
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 3 deletions.
40 changes: 37 additions & 3 deletions types/primitives/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ import (

var hextable = "0123456789abcdef"

const nonHexMarker = 100

var reverseHexTable [256]byte

func init() {
// Pre-fill the reverseHexTable
for i := 0; i < len(reverseHexTable); i++ {
switch {
case i >= '0' && i <= '9':
reverseHexTable[i] = byte(i) - '0'
case i >= 'a' && i <= 'f':
reverseHexTable[i] = byte(i) - 'a' + 10
case i >= 'A' && i <= 'F':
reverseHexTable[i] = byte(i) - 'A' + 10
default:
reverseHexTable[i] = nonHexMarker
}
}
}

func alignBytes(b []byte, length int, bigEndian bool) []byte {
if length < 0 {
if b == nil {
Expand Down Expand Up @@ -72,8 +92,22 @@ func hexToBytes(s string) ([]byte, error) {
if len(s) >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
s = s[2:]
}
if len(s)%2 == 1 {
s = "0" + s

if len(s)%2 == 0 {
return hex.DecodeString(s)
}
return hex.DecodeString(s)

dst := make([]byte, hex.DecodedLen(1+len(s)))

if reverseHexTable[s[0]] == nonHexMarker {
return nil, hex.InvalidByteError(s[0])
}
dst[0] = reverseHexTable[s[0]]

// Allocation-free string -> []byte conversion there (compiler will optimize it out)
if _, err := hex.Decode(dst[1:], []byte(s[1:])); err != nil {
return nil, err
}

return dst, nil
}
93 changes: 93 additions & 0 deletions types/primitives/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,96 @@ func TestBytesToHex(t *testing.T) {
assert.Equal(t, "0x0000000010", string(writeDataHex(nil, []byte{0, 0, 0, 0, 16})))
assert.Equal(t, "0x10", string(writeQuantityHex(nil, []byte{0, 0, 0, 0, 16})))
}

func TestHexToBytes(t *testing.T) {
testCases := []struct {
name string
in string
out []byte
err bool
}{{
name: "empty",
in: "",
out: []byte{},
}, {
name: "empty with prefix",
in: "0x",
out: []byte{},
}, {
name: "zero without prefix",
in: "00",
out: []byte{0x00},
}, {
name: "even length without prefix",
in: "1a",
out: []byte{0x1a},
}, {
name: "uppercase without prefix",
in: "FC",
out: []byte{0xfc},
}, {
name: "longer, without prefix",
in: "0a1b2c",
out: []byte{0x0a, 0x1b, 0x2c},
}, {
name: "zero with prefix",
in: "0x00",
out: []byte{0x00},
}, {
name: "even length with prefix",
in: "0x1a",
out: []byte{0x1a},
}, {
name: "uppercase with prefix",
in: "0XFC",
out: []byte{0xfc},
}, {
name: "zero with odd length",
in: "0x0",
out: []byte{0x00},
}, {
name: "odd length without prefix",
in: "a",
out: []byte{0x0a},
}, {
name: "uppercase with odd length",
in: "0XFC1",
out: []byte{0x0f, 0xc1},
}, {
name: "longer, with odd length",
in: "0xa1b2c",
out: []byte{0x0a, 0x1b, 0x2c},
}, {
name: "only invalid characters",
in: "0xgg",
err: true,
}, {
name: "only invalid characters without prefix",
in: "gg",
err: true,
}, {
name: "mixed invalid characters, with odd length",
in: "0x1b0gg",
err: true,
}, {
name: "with whitespace",
in: "0x1 a",
err: true,
}, {
name: "non-ascii characters",
in: "0x(╯°□°)╯ ┻━┻",
err: true,
}}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := hexToBytes(tc.in)
if tc.err {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.out, res)
}
})
}
}

0 comments on commit 674a72f

Please sign in to comment.