aboutsummaryrefslogtreecommitdiffstats
path: root/common/hexutil/json.go
diff options
context:
space:
mode:
Diffstat (limited to 'common/hexutil/json.go')
-rw-r--r--common/hexutil/json.go271
1 files changed, 271 insertions, 0 deletions
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
new file mode 100644
index 000000000..cbbadbed6
--- /dev/null
+++ b/common/hexutil/json.go
@@ -0,0 +1,271 @@
+// 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 (
+ jsonNull = []byte("null")
+ jsonZero = []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
+
+// MarshalJSON implements json.Marshaler.
+func (b Bytes) MarshalJSON() ([]byte, error) {
+ result := make([]byte, len(b)*2+4)
+ copy(result, `"0x`)
+ hex.Encode(result[3:], b)
+ result[len(result)-1] = '"'
+ return result, nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *Bytes) UnmarshalJSON(input []byte) error {
+ raw, err := checkJSON(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)
+}
+
+// UnmarshalJSON decodes input as a JSON string with 0x prefix. The length of out
+// determines the required input length. This function is commonly used to implement the
+// UnmarshalJSON method for fixed-size types:
+//
+// type Foo [8]byte
+//
+// func (f *Foo) UnmarshalJSON(input []byte) error {
+// return hexutil.UnmarshalJSON("Foo", input, f[:])
+// }
+func UnmarshalJSON(typname string, input, out []byte) error {
+ raw, err := checkJSON(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.
+type Big big.Int
+
+// MarshalJSON implements json.Marshaler.
+func (b *Big) MarshalJSON() ([]byte, error) {
+ if b == nil {
+ return jsonNull, nil
+ }
+ bigint := (*big.Int)(b)
+ if bigint.Sign() == -1 {
+ return nil, errNegativeBigInt
+ }
+ nbits := bigint.BitLen()
+ if nbits == 0 {
+ return jsonZero, nil
+ }
+ enc := make([]byte, 3, (nbits/8)*2+4)
+ copy(enc, `"0x`)
+ for i := len(bigint.Bits()) - 1; i >= 0; i-- {
+ enc = strconv.AppendUint(enc, uint64(bigint.Bits()[i]), 16)
+ }
+ enc = append(enc, '"')
+ return enc, nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *Big) UnmarshalJSON(input []byte) error {
+ raw, err := checkNumberJSON(input)
+ if err != nil {
+ return err
+ }
+ 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
+
+// MarshalJSON implements json.Marshaler.
+func (b Uint64) MarshalJSON() ([]byte, error) {
+ buf := make([]byte, 3, 12)
+ copy(buf, `"0x`)
+ buf = strconv.AppendUint(buf, uint64(b), 16)
+ buf = append(buf, '"')
+ return buf, nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *Uint64) UnmarshalJSON(input []byte) error {
+ raw, err := checkNumberJSON(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
+
+// MarshalJSON implements json.Marshaler.
+func (b Uint) MarshalJSON() ([]byte, error) {
+ return Uint64(b).MarshalJSON()
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *Uint) UnmarshalJSON(input []byte) error {
+ var u64 Uint64
+ err := u64.UnmarshalJSON(input)
+ if err != nil {
+ return err
+ } else if u64 > Uint64(^uint(0)) {
+ return ErrUintRange
+ }
+ *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 checkJSON(input []byte) (raw []byte, err error) {
+ if !isString(input) {
+ return nil, errNonString
+ }
+ if len(input) == 2 {
+ return nil, ErrEmptyString
+ }
+ if !bytesHave0xPrefix(input[1:]) {
+ return nil, ErrMissingPrefix
+ }
+ input = input[3 : len(input)-1]
+ if len(input)%2 != 0 {
+ return nil, ErrOddLength
+ }
+ return input, nil
+}
+
+func checkNumberJSON(input []byte) (raw []byte, err error) {
+ if !isString(input) {
+ return nil, errNonString
+ }
+ input = input[1 : len(input)-1]
+ if len(input) == 0 {
+ return nil, ErrEmptyString
+ }
+ 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
+}