github.com/MetalBlockchain/metalgo@v1.11.9/utils/formatting/encoding.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package formatting 5 6 import ( 7 "bytes" 8 "encoding/hex" 9 "errors" 10 "fmt" 11 "math" 12 "strings" 13 14 "github.com/MetalBlockchain/metalgo/utils/hashing" 15 ) 16 17 const ( 18 hexPrefix = "0x" 19 checksumLen = 4 20 ) 21 22 var ( 23 errEncodingOverFlow = errors.New("encoding overflow") 24 errInvalidEncoding = errors.New("invalid encoding") 25 errUnsupportedEncodingInMethod = errors.New("unsupported encoding in method") 26 errMissingChecksum = errors.New("input string is smaller than the checksum size") 27 errBadChecksum = errors.New("invalid input checksum") 28 errMissingHexPrefix = errors.New("missing 0x prefix to hex encoding") 29 ) 30 31 // Encoding defines how bytes are converted to a string and vice versa 32 type Encoding uint8 33 34 const ( 35 // Hex specifies a hex plus 4 byte checksum encoding format 36 Hex Encoding = iota 37 // HexNC specifies a hex encoding format 38 HexNC 39 // HexC specifies a hex plus 4 byte checksum encoding format 40 HexC 41 // JSON specifies the JSON encoding format 42 JSON 43 ) 44 45 func (enc Encoding) String() string { 46 switch enc { 47 case Hex: 48 return "hex" 49 case HexNC: 50 return "hexnc" 51 case HexC: 52 return "hexc" 53 case JSON: 54 return "json" 55 default: 56 return errInvalidEncoding.Error() 57 } 58 } 59 60 func (enc Encoding) valid() bool { 61 switch enc { 62 case Hex, HexNC, HexC, JSON: 63 return true 64 } 65 return false 66 } 67 68 func (enc Encoding) MarshalJSON() ([]byte, error) { 69 if !enc.valid() { 70 return nil, errInvalidEncoding 71 } 72 return []byte(`"` + enc.String() + `"`), nil 73 } 74 75 func (enc *Encoding) UnmarshalJSON(b []byte) error { 76 str := string(b) 77 if str == "null" { 78 return nil 79 } 80 switch strings.ToLower(str) { 81 case `"hex"`: 82 *enc = Hex 83 case `"hexnc"`: 84 *enc = HexNC 85 case `"hexc"`: 86 *enc = HexC 87 case `"json"`: 88 *enc = JSON 89 default: 90 return errInvalidEncoding 91 } 92 return nil 93 } 94 95 // Encode [bytes] to a string using the given encoding format [bytes] may be 96 // nil, in which case it will be treated the same as an empty slice. 97 func Encode(encoding Encoding, bytes []byte) (string, error) { 98 if !encoding.valid() { 99 return "", errInvalidEncoding 100 } 101 102 switch encoding { 103 case Hex, HexC: 104 bytesLen := len(bytes) 105 if bytesLen > math.MaxInt32-checksumLen { 106 return "", errEncodingOverFlow 107 } 108 checked := make([]byte, bytesLen+checksumLen) 109 copy(checked, bytes) 110 copy(checked[len(bytes):], hashing.Checksum(bytes, checksumLen)) 111 bytes = checked 112 } 113 114 switch encoding { 115 case Hex, HexNC, HexC: 116 return fmt.Sprintf("0x%x", bytes), nil 117 case JSON: 118 // JSON Marshal does not support []byte input and we rely on the 119 // router's json marshalling to marshal our interface{} into JSON 120 // in response. Therefore it is not supported in this call. 121 return "", errUnsupportedEncodingInMethod 122 default: 123 return "", errInvalidEncoding 124 } 125 } 126 127 // Decode [str] to bytes using the given encoding 128 // If [str] is the empty string, returns a nil byte slice and nil error 129 func Decode(encoding Encoding, str string) ([]byte, error) { 130 switch { 131 case !encoding.valid(): 132 return nil, errInvalidEncoding 133 // TODO: remove the empty string check and enforce the correct format. 134 case len(str) == 0: 135 return nil, nil 136 } 137 138 var ( 139 decodedBytes []byte 140 err error 141 ) 142 switch encoding { 143 case Hex, HexNC, HexC: 144 if !strings.HasPrefix(str, hexPrefix) { 145 return nil, errMissingHexPrefix 146 } 147 decodedBytes, err = hex.DecodeString(str[2:]) 148 case JSON: 149 // JSON unmarshalling requires interface and has no return values 150 // contrary to this method, therefore it is not supported in this call 151 return nil, errUnsupportedEncodingInMethod 152 default: 153 return nil, errInvalidEncoding 154 } 155 if err != nil { 156 return nil, err 157 } 158 159 switch encoding { 160 case Hex, HexC: 161 if len(decodedBytes) < checksumLen { 162 return nil, errMissingChecksum 163 } 164 // Verify the checksum 165 rawBytes := decodedBytes[:len(decodedBytes)-checksumLen] 166 checksum := decodedBytes[len(decodedBytes)-checksumLen:] 167 if !bytes.Equal(checksum, hashing.Checksum(rawBytes, checksumLen)) { 168 return nil, errBadChecksum 169 } 170 decodedBytes = rawBytes 171 } 172 return decodedBytes, nil 173 }