d304da3803
This commit makes the wrapper types more generally applicable. encoding.TextMarshaler is supported by most codec implementations (e.g. for yaml). The tests now ensure that package json actually recognizes the custom marshaler implementation irrespective of how it is implemented. The Uint type has new tests, too. These are tricky because uint size depends on the CPU word size. Turns out that there was one incorrect case where decoding returned ErrUint64Range instead of ErrUintRange.
288 lines
7.0 KiB
Go
288 lines
7.0 KiB
Go
// Copyright 2016 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package hexutil
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
)
|
|
|
|
var (
|
|
textZero = []byte(`0x0`)
|
|
errNonString = errors.New("cannot unmarshal non-string as hex data")
|
|
errNegativeBigInt = errors.New("hexutil.Big: can't marshal negative integer")
|
|
)
|
|
|
|
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
|
|
// The empty slice marshals as "0x".
|
|
type Bytes []byte
|
|
|
|
// MarshalText implements encoding.TextMarshaler
|
|
func (b Bytes) MarshalText() ([]byte, error) {
|
|
result := make([]byte, len(b)*2+2)
|
|
copy(result, `0x`)
|
|
hex.Encode(result[2:], b)
|
|
return result, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (b *Bytes) UnmarshalJSON(input []byte) error {
|
|
if !isString(input) {
|
|
return errNonString
|
|
}
|
|
return b.UnmarshalText(input[1 : len(input)-1])
|
|
}
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
func (b *Bytes) UnmarshalText(input []byte) error {
|
|
raw, err := checkText(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dec := make([]byte, len(raw)/2)
|
|
if _, err = hex.Decode(dec, raw); err != nil {
|
|
err = mapError(err)
|
|
} else {
|
|
*b = dec
|
|
}
|
|
return err
|
|
}
|
|
|
|
// String returns the hex encoding of b.
|
|
func (b Bytes) String() string {
|
|
return Encode(b)
|
|
}
|
|
|
|
// UnmarshalFixedText decodes the input as a string with 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 UnmarshalFixedText(typname string, input, out []byte) error {
|
|
raw, err := checkText(input)
|
|
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
|
|
}
|
|
|
|
// Big marshals/unmarshals as a JSON string with 0x prefix.
|
|
// The zero value marshals as "0x0".
|
|
//
|
|
// Negative integers are not supported at this time. Attempting to marshal them will
|
|
// return an error. Values larger than 256bits are rejected by Unmarshal but will be
|
|
// marshaled without error.
|
|
type Big big.Int
|
|
|
|
// MarshalText implements encoding.TextMarshaler
|
|
func (b Big) MarshalText() ([]byte, error) {
|
|
bigint := (big.Int)(b)
|
|
if bigint.Sign() == -1 {
|
|
return nil, errNegativeBigInt
|
|
}
|
|
nbits := bigint.BitLen()
|
|
if nbits == 0 {
|
|
return textZero, nil
|
|
}
|
|
enc := make([]byte, 2, nbits/4+2)
|
|
copy(enc, "0x")
|
|
enc = bigint.Append(enc, 16)
|
|
return enc, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (b *Big) UnmarshalJSON(input []byte) error {
|
|
if !isString(input) {
|
|
return errNonString
|
|
}
|
|
return b.UnmarshalText(input[1 : len(input)-1])
|
|
}
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler
|
|
func (b *Big) UnmarshalText(input []byte) error {
|
|
raw, err := checkNumberText(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(raw) > 64 {
|
|
return ErrBig256Range
|
|
}
|
|
words := make([]big.Word, len(raw)/bigWordNibbles+1)
|
|
end := len(raw)
|
|
for i := range words {
|
|
start := end - bigWordNibbles
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
for ri := start; ri < end; ri++ {
|
|
nib := decodeNibble(raw[ri])
|
|
if nib == badNibble {
|
|
return ErrSyntax
|
|
}
|
|
words[i] *= 16
|
|
words[i] += big.Word(nib)
|
|
}
|
|
end = start
|
|
}
|
|
var dec big.Int
|
|
dec.SetBits(words)
|
|
*b = (Big)(dec)
|
|
return nil
|
|
}
|
|
|
|
// ToInt converts b to a big.Int.
|
|
func (b *Big) ToInt() *big.Int {
|
|
return (*big.Int)(b)
|
|
}
|
|
|
|
// String returns the hex encoding of b.
|
|
func (b *Big) String() string {
|
|
return EncodeBig(b.ToInt())
|
|
}
|
|
|
|
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
|
|
// The zero value marshals as "0x0".
|
|
type Uint64 uint64
|
|
|
|
// MarshalText implements encoding.TextMarshaler.
|
|
func (b Uint64) MarshalText() ([]byte, error) {
|
|
buf := make([]byte, 2, 10)
|
|
copy(buf, `0x`)
|
|
buf = strconv.AppendUint(buf, uint64(b), 16)
|
|
return buf, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (b *Uint64) UnmarshalJSON(input []byte) error {
|
|
if !isString(input) {
|
|
return errNonString
|
|
}
|
|
return b.UnmarshalText(input[1 : len(input)-1])
|
|
}
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler
|
|
func (b *Uint64) UnmarshalText(input []byte) error {
|
|
raw, err := checkNumberText(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(raw) > 16 {
|
|
return ErrUint64Range
|
|
}
|
|
var dec uint64
|
|
for _, byte := range raw {
|
|
nib := decodeNibble(byte)
|
|
if nib == badNibble {
|
|
return ErrSyntax
|
|
}
|
|
dec *= 16
|
|
dec += uint64(nib)
|
|
}
|
|
*b = Uint64(dec)
|
|
return nil
|
|
}
|
|
|
|
// String returns the hex encoding of b.
|
|
func (b Uint64) String() string {
|
|
return EncodeUint64(uint64(b))
|
|
}
|
|
|
|
// Uint marshals/unmarshals as a JSON string with 0x prefix.
|
|
// The zero value marshals as "0x0".
|
|
type Uint uint
|
|
|
|
// MarshalText implements encoding.TextMarshaler.
|
|
func (b Uint) MarshalText() ([]byte, error) {
|
|
return Uint64(b).MarshalText()
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (b *Uint) UnmarshalJSON(input []byte) error {
|
|
if !isString(input) {
|
|
return errNonString
|
|
}
|
|
return b.UnmarshalText(input[1 : len(input)-1])
|
|
}
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
func (b *Uint) UnmarshalText(input []byte) error {
|
|
var u64 Uint64
|
|
err := u64.UnmarshalText(input)
|
|
if u64 > Uint64(^uint(0)) || err == ErrUint64Range {
|
|
return ErrUintRange
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
*b = Uint(u64)
|
|
return nil
|
|
}
|
|
|
|
// String returns the hex encoding of b.
|
|
func (b Uint) String() string {
|
|
return EncodeUint64(uint64(b))
|
|
}
|
|
|
|
func isString(input []byte) bool {
|
|
return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"'
|
|
}
|
|
|
|
func bytesHave0xPrefix(input []byte) bool {
|
|
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
|
|
}
|
|
|
|
func checkText(input []byte) ([]byte, error) {
|
|
if len(input) == 0 {
|
|
return nil, nil // empty strings are allowed
|
|
}
|
|
if !bytesHave0xPrefix(input) {
|
|
return nil, ErrMissingPrefix
|
|
}
|
|
input = input[2:]
|
|
if len(input)%2 != 0 {
|
|
return nil, ErrOddLength
|
|
}
|
|
return input, nil
|
|
}
|
|
|
|
func checkNumberText(input []byte) (raw []byte, err error) {
|
|
if len(input) == 0 {
|
|
return nil, nil // empty strings are allowed
|
|
}
|
|
if !bytesHave0xPrefix(input) {
|
|
return nil, ErrMissingPrefix
|
|
}
|
|
input = input[2:]
|
|
if len(input) == 0 {
|
|
return nil, ErrEmptyNumber
|
|
}
|
|
if len(input) > 1 && input[0] == '0' {
|
|
return nil, ErrLeadingZero
|
|
}
|
|
return input, nil
|
|
}
|