github.com/amp-space/amp-sdk-go@v0.7.6/stdlib/bufs/encoding.go (about) 1 package bufs 2 3 import ( 4 "encoding/base32" 5 "encoding/hex" 6 "encoding/json" 7 8 //"github.com/mmcloughlin/geohash" 9 10 "reflect" 11 ) 12 13 // GeohashBase32Alphabet is the standard geo-hash alphabet used for Base32Encoding. 14 // It chooses particular characters that are not visually similar to each other. 15 const GeohashBase32Alphabet = "0123456789bcdefghjkmnpqrstuvwxyz" 16 17 var ( 18 // Base32Encoding is used to encode/decode binary buffer to/from base 32 19 Base32Encoding = base32.NewEncoding(GeohashBase32Alphabet).WithPadding(base32.NoPadding) 20 21 // GenesisMemberID is the genesis member ID 22 GenesisMemberID = uint32(1) 23 ) 24 25 // Zero zeros out a given slice 26 func Zero(buf []byte) { 27 N := int32(len(buf)) 28 for i := int32(0); i < N; i++ { 29 buf[i] = 0 30 } 31 } 32 33 // Marshaler generalizes efficient serialization 34 type Marshaler interface { 35 Marshal() ([]byte, error) 36 MarshalToSizedBuffer([]byte) (int, error) 37 Size() int 38 } 39 40 // Unmarshaler used to generalize deserialization 41 type Unmarshaler interface { 42 Unmarshal([]byte) error 43 } 44 45 // SmartMarshal marshals the given item to the given buffer. If there is not enough space a new one is allocated. The purpose of this is to reuse a scrap buffer. 46 func SmartMarshal(item Marshaler, tryDst []byte) []byte { 47 bufSz := cap(tryDst) 48 encSz := item.Size() 49 if encSz > bufSz { 50 bufSz = (encSz + 15) &^ 15 51 tryDst = make([]byte, bufSz) 52 } 53 54 var err error 55 encSz, err = item.MarshalToSizedBuffer(tryDst[:encSz]) 56 if err != nil { 57 panic(err) 58 } 59 60 return tryDst[:encSz] 61 } 62 63 // SmartMarshalToBase32 marshals the given item and then encodes it into a base32 (ASCII) byte string. 64 // 65 // If tryDst is not large enough, a new buffer is allocated and returned in its place. 66 func SmartMarshalToBase32(item Marshaler, tryDst []byte) []byte { 67 bufSz := cap(tryDst) 68 binSz := item.Size() 69 { 70 safeSz := 4 + 4*((binSz+2)/3) 71 if safeSz > bufSz { 72 bufSz = (safeSz + 7) &^ 7 73 tryDst = make([]byte, bufSz) 74 } 75 } 76 77 // First, marshal the item to the right-side of the scrap buffer 78 binBuf := tryDst[bufSz-binSz : bufSz] 79 var err error 80 binSz, err = item.MarshalToSizedBuffer(binBuf) 81 if err != nil { 82 panic(err) 83 } 84 85 // Now encode the marshaled to the left side of the scrap buffer. 86 // There is overlap, but encoding consumes from left to right, so it's safe. 87 encSz := Base32Encoding.EncodedLen(binSz) 88 tryDst = tryDst[:encSz] 89 Base32Encoding.Encode(tryDst, binBuf[:binSz]) 90 91 return tryDst 92 } 93 94 // SmartDecodeFromBase32 decodes the base32 (ASCII) string into the given scrap buffer, returning the scrap buffer set to proper size. 95 // 96 // If tryDst is not large enough, a new buffer is allocated and returned in its place. 97 func SmartDecodeFromBase32(srcBase32 []byte, tryDst []byte) ([]byte, error) { 98 binSz := Base32Encoding.DecodedLen(len(srcBase32)) 99 100 bufSz := cap(tryDst) 101 if binSz > bufSz { 102 bufSz = (binSz + 7) &^ 7 103 tryDst = make([]byte, bufSz) 104 } 105 var err error 106 binSz, err = Base32Encoding.Decode(tryDst[:binSz], srcBase32) 107 return tryDst[:binSz], err 108 } 109 110 // Buf is a flexible buffer designed for reuse. 111 type Buf struct { 112 Unmarshaler 113 114 Bytes []byte 115 } 116 117 // Unmarshal effectively copies the src buffer. 118 func (buf *Buf) Unmarshal(srcBuf []byte) error { 119 N := len(srcBuf) 120 if cap(buf.Bytes) < N { 121 allocSz := ((N + 127) >> 7) << 7 122 buf.Bytes = make([]byte, N, allocSz) 123 } else { 124 buf.Bytes = buf.Bytes[:N] 125 } 126 copy(buf.Bytes, srcBuf) 127 128 return nil 129 } 130 131 var ( 132 bytesType = reflect.TypeOf(Bytes(nil)) 133 ) 134 135 // Bytes marshal/unmarshal as a JSON string with 0x prefix. 136 // The empty slice marshals as "0x". 137 type Bytes []byte 138 139 // MarshalText implements encoding.TextMarshaler 140 func (b Bytes) MarshalText() ([]byte, error) { 141 out := make([]byte, len(b)*2+2) 142 out[0] = '0' 143 out[1] = 'x' 144 hex.Encode(out[2:], b) 145 return out, nil 146 } 147 148 // UnmarshalJSON implements json.Unmarshaler. 149 func (b *Bytes) UnmarshalJSON(in []byte) error { 150 if !isString(in) { 151 return errNonString(bytesType) 152 } 153 return wrapTypeError(b.UnmarshalText(in[1:len(in)-1]), bytesType) 154 } 155 156 // UnmarshalText implements encoding.TextUnmarshaler. 157 func (b *Bytes) UnmarshalText(input []byte) error { 158 raw, err := checkText(input) 159 if err != nil { 160 return err 161 } 162 dec := make([]byte, len(raw)/2) 163 if _, err = hex.Decode(dec, raw); err == nil { 164 *b = dec 165 } 166 return err 167 } 168 169 // String returns the hex encoding of b. 170 func (b Bytes) String() string { 171 out := make([]byte, len(b)*2+2) 172 out[0] = '0' 173 out[1] = 'x' 174 hex.Encode(out[2:], b) 175 return string(out) 176 } 177 178 type encodingErr struct { 179 msg string 180 } 181 182 func (err *encodingErr) Error() string { 183 return err.msg 184 } 185 186 // Errors 187 var ( 188 ErrSyntax = &encodingErr{"invalid hex string"} 189 ) 190 191 func isString(input []byte) bool { 192 return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' 193 } 194 195 func wrapTypeError(err error, typ reflect.Type) error { 196 if _, ok := err.(*encodingErr); ok { 197 return &json.UnmarshalTypeError{Value: err.Error(), Type: typ} 198 } 199 return err 200 } 201 202 func errNonString(typ reflect.Type) error { 203 return &json.UnmarshalTypeError{Value: "non-string", Type: typ} 204 } 205 206 func checkText(in []byte) ([]byte, error) { 207 N := len(in) 208 if N == 0 { 209 return nil, nil // empty strings are allowed 210 } 211 if N >= 2 && in[0] == '0' && (in[1] == 'x' || in[1] == 'X') { 212 in = in[2:] 213 N -= 2 214 } 215 return in, nil 216 } 217 218 // BufDesc returns a base32 encoding of a binary string, limiting it to a short number of character for debugging and logging. 219 func BufDesc(inBuf []byte) string { 220 if len(inBuf) == 0 { 221 return "nil" 222 } 223 224 buf := inBuf 225 226 const limit = 12 227 alreadyASCII := true 228 for _, b := range buf { 229 if b < 32 || b > 126 { 230 alreadyASCII = false 231 break 232 } 233 } 234 235 suffix := "" 236 if len(buf) > limit { 237 buf = buf[:limit] 238 suffix = "…" 239 } 240 241 outStr := "" 242 if alreadyASCII { 243 outStr = string(buf) 244 } else { 245 outStr = Base32Encoding.EncodeToString(buf) 246 } 247 248 return outStr + suffix 249 }