github.com/git-amp/amp-sdk-go@v0.7.5/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  }