common/hexutil: add UnmarshalFixedUnprefixedText

This commit is contained in:
Felix Lange 2017-03-07 23:19:27 +01:00
parent 04fa6a3744
commit b4547a560b
2 changed files with 62 additions and 5 deletions

View File

@ -51,7 +51,7 @@ func (b *Bytes) UnmarshalJSON(input []byte) error {
// UnmarshalText implements encoding.TextUnmarshaler. // UnmarshalText implements encoding.TextUnmarshaler.
func (b *Bytes) UnmarshalText(input []byte) error { func (b *Bytes) UnmarshalText(input []byte) error {
raw, err := checkText(input) raw, err := checkText(input, true)
if err != nil { if err != nil {
return err return err
} }
@ -73,7 +73,28 @@ func (b Bytes) String() string {
// determines the required input length. This function is commonly used to implement the // determines the required input length. This function is commonly used to implement the
// UnmarshalText method for fixed-size types. // UnmarshalText method for fixed-size types.
func UnmarshalFixedText(typname string, input, out []byte) error { func UnmarshalFixedText(typname string, input, out []byte) error {
raw, err := checkText(input) raw, err := checkText(input, true)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
hex.Decode(out, raw)
return nil
}
// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The
// length of out determines the required input length. This function is commonly used to
// implement the UnmarshalText method for fixed-size types.
func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error {
raw, err := checkText(input, false)
if err != nil { if err != nil {
return err return err
} }
@ -243,14 +264,15 @@ func bytesHave0xPrefix(input []byte) bool {
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
} }
func checkText(input []byte) ([]byte, error) { func checkText(input []byte, wantPrefix bool) ([]byte, error) {
if len(input) == 0 { if len(input) == 0 {
return nil, nil // empty strings are allowed return nil, nil // empty strings are allowed
} }
if !bytesHave0xPrefix(input) { if bytesHave0xPrefix(input) {
input = input[2:]
} else if wantPrefix {
return nil, ErrMissingPrefix return nil, ErrMissingPrefix
} }
input = input[2:]
if len(input)%2 != 0 { if len(input)%2 != 0 {
return nil, ErrOddLength return nil, ErrOddLength
} }

View File

@ -337,3 +337,38 @@ func TestUnmarshalUint(t *testing.T) {
} }
} }
} }
func TestUnmarshalFixedUnprefixedText(t *testing.T) {
tests := []struct {
input string
want []byte
wantErr error
}{
{input: "0x2", wantErr: ErrOddLength},
{input: "2", wantErr: ErrOddLength},
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
// check that output is not modified for partially correct input
{input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
{input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
// valid inputs
{input: "44444444", want: []byte{0x44, 0x44, 0x44, 0x44}},
{input: "0x44444444", want: []byte{0x44, 0x44, 0x44, 0x44}},
}
for _, test := range tests {
out := make([]byte, 4)
err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out)
switch {
case err == nil && test.wantErr != nil:
t.Errorf("%q: got no error, expected %q", test.input, test.wantErr)
case err != nil && test.wantErr == nil:
t.Errorf("%q: unexpected error %q", test.input, err)
case err != nil && err.Error() != test.wantErr.Error():
t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr)
}
if test.want != nil && !bytes.Equal(out, test.want) {
t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want)
}
}
}