github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/cos/bytepack.go (about)

     1  // Package cos provides common low-level types and utilities for all aistore projects
     2  /*
     3   * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package cos
     6  
     7  import (
     8  	"encoding/binary"
     9  
    10  	"github.com/NVIDIA/aistore/cmn/debug"
    11  )
    12  
    13  // The module provides a way to encode/decode data as a compact binary slice.
    14  // Comparison with JSON:
    15  // Pros:
    16  //  - more compact
    17  //  - no reflection
    18  //  - private fields can be serialized
    19  //  - allows to pack a few separate structs or mix structs with PODs in
    20  //    the same packet (impossible in JSON)
    21  //  - depending on a value of a struct field you can implement
    22  //    different pack/unpack functions to use only selected fields
    23  // Cons:
    24  //  - no type checking: garbage in, garbage out. JSON can skip invalid fields
    25  //  - every struct that needs binary serialization must implement
    26  //    both interfaces: Packer and Unpacker
    27  //  - no automatic struct marshal/unmarshal. It is caller responsibility
    28  //    to allocate long enough buffer for packing and for saving/restoring the
    29  //    fields/structs in correct order
    30  //  - in general, a field cannot be omitted. It can be simulated with markers.
    31  //    Please, read tests/bytepack_test.go to see how to save/restore inner
    32  //    pointer to a structure with a marker.
    33  //    Hint: look for a field "parent".
    34  //
    35  // Notes on size calculation:
    36  //  - for POD types: use constants from `cmn`: look for `Sizeof..` ones
    37  //  - for strings and slices: do not forget to add size of length marker to
    38  //    final PackedSize result for every slice and string. Never use SizeofI64
    39  //    or any other integer size, use only `SizeofLen` that always represents
    40  //    the current length of a size marker in the binary
    41  //  - if a struct has an inner struct: do not forget to add its size to total
    42  //
    43  // How to use it:
    44  // Packing:
    45  // 1. Implement interfaces for all struct that are going to use binary packing
    46  // 2. Add a method to a caller that calculates total pack size and allocates
    47  //    a buffer for packing. It is OK to just allocate/use a buffer with
    48  //    predefined big size.
    49  // 3. Write all data to the create packer (cos.NewPacker)
    50  // 4. When all data is written, get the packed data with `packer.Bytes()`.
    51  // 5. Send this value as a regular `[]byte` slice
    52  // NOTE: if you need to know how many bytes the packer contains, use
    53  //    `len(packer.Bytes())` - `packer.Bytes` is a cheap operation as it
    54  //    return a slice of internal byte array
    55  // Unpacking:
    56  // 1. Read `[]byte` from the source
    57  // 2. Create unpacker with `NewUnpacker([]byte)`
    58  // 3. Read data from unpacker in the same order you put the data in `Packing`
    59  // 4. Reading never panics, so check read result for `error==nil` to be sure
    60  //    that the data have been read
    61  
    62  type (
    63  	BytePack struct {
    64  		b   []byte
    65  		off int
    66  	}
    67  
    68  	ByteUnpack struct {
    69  		b   []byte
    70  		off int
    71  	}
    72  
    73  	MapStrUint16 map[string]uint16
    74  
    75  	// Every object that is going to use binary representation instead of
    76  	// JSON must implement two following methods
    77  	Unpacker interface {
    78  		Unpack(unpacker *ByteUnpack) error
    79  	}
    80  
    81  	Packer interface {
    82  		Pack(packer *BytePack)
    83  		PackedSize() int
    84  	}
    85  )
    86  
    87  // Internally size of byte slice or string is packed as this integer type.
    88  // Since binary packed structs are used for fairly small amount of data,
    89  // it is possible to use int32 instead of int64 to keep the length of the data.
    90  const SizeofLen = SizeofI32
    91  
    92  // PackedStrLen returns the size occupied by a given string in the output
    93  func PackedStrLen(s string) int {
    94  	return SizeofLen + len(s)
    95  }
    96  
    97  func NewUnpacker(buf []byte) *ByteUnpack {
    98  	return &ByteUnpack{b: buf}
    99  }
   100  
   101  func NewPacker(buf []byte, bufLen int) *BytePack {
   102  	if buf == nil {
   103  		return &BytePack{b: make([]byte, bufLen)}
   104  	}
   105  	return &BytePack{b: buf}
   106  }
   107  
   108  //
   109  // Unpacker
   110  //
   111  
   112  func (br *ByteUnpack) Bytes() []byte {
   113  	return br.b
   114  }
   115  
   116  func (br *ByteUnpack) ReadByte() (byte, error) {
   117  	if br.off >= len(br.b) {
   118  		return 0, errBufferUnderrun
   119  	}
   120  	debug.Assert(br.off < len(br.b))
   121  	b := br.b[br.off]
   122  	br.off++
   123  	return b, nil
   124  }
   125  
   126  func (br *ByteUnpack) ReadBool() (bool, error) {
   127  	bt, err := br.ReadByte()
   128  	return bt != 0, err
   129  }
   130  
   131  func (br *ByteUnpack) ReadInt64() (int64, error) {
   132  	n, err := br.ReadUint64()
   133  	return int64(n), err
   134  }
   135  
   136  func (br *ByteUnpack) ReadUint64() (uint64, error) {
   137  	if len(br.b)-br.off < SizeofI64 {
   138  		return 0, errBufferUnderrun
   139  	}
   140  	n := binary.BigEndian.Uint64(br.b[br.off:])
   141  	br.off += SizeofI64
   142  	return n, nil
   143  }
   144  
   145  func (br *ByteUnpack) ReadInt16() (int16, error) {
   146  	n, err := br.ReadUint16()
   147  	return int16(n), err
   148  }
   149  
   150  func (br *ByteUnpack) ReadUint16() (uint16, error) {
   151  	if len(br.b)-br.off < SizeofI16 {
   152  		return 0, errBufferUnderrun
   153  	}
   154  	n := binary.BigEndian.Uint16(br.b[br.off:])
   155  	br.off += SizeofI16
   156  	return n, nil
   157  }
   158  
   159  func (br *ByteUnpack) ReadInt32() (int32, error) {
   160  	n, err := br.ReadUint32()
   161  	return int32(n), err
   162  }
   163  
   164  func (br *ByteUnpack) ReadUint32() (uint32, error) {
   165  	if len(br.b)-br.off < SizeofI32 {
   166  		return 0, errBufferUnderrun
   167  	}
   168  	n := binary.BigEndian.Uint32(br.b[br.off:])
   169  	br.off += SizeofI32
   170  	return n, nil
   171  }
   172  
   173  func (br *ByteUnpack) ReadBytes() ([]byte, error) {
   174  	if len(br.b)-br.off < SizeofLen {
   175  		return nil, errBufferUnderrun
   176  	}
   177  	l, err := br.ReadUint32()
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	if len(br.b)-br.off < int(l) {
   182  		return nil, errBufferUnderrun
   183  	}
   184  	start := br.off
   185  	br.off += int(l)
   186  	return br.b[start : start+int(l)], nil
   187  }
   188  
   189  func (br *ByteUnpack) ReadString() (string, error) {
   190  	bytes, err := br.ReadBytes()
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  	return string(bytes), nil
   195  }
   196  
   197  func (br *ByteUnpack) ReadAny(st Unpacker) error {
   198  	return st.Unpack(br)
   199  }
   200  
   201  func (br *ByteUnpack) ReadMapStrUint16() (MapStrUint16, error) {
   202  	l, err := br.ReadInt32()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	mp := make(MapStrUint16, l)
   207  	for ; l > 0; l-- {
   208  		key, err := br.ReadString()
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  		val, err := br.ReadUint16()
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		mp[key] = val
   217  	}
   218  	return mp, nil
   219  }
   220  
   221  func (br *ByteUnpack) Len() int { return len(br.b) - br.off }
   222  
   223  //
   224  // Packer
   225  //
   226  
   227  func (bw *BytePack) WriteByte(b byte) {
   228  	bw.b[bw.off] = b
   229  	bw.off++
   230  }
   231  
   232  func (bw *BytePack) WriteBool(b bool) {
   233  	if b {
   234  		bw.b[bw.off] = 1
   235  	} else {
   236  		bw.b[bw.off] = 0
   237  	}
   238  	bw.off++
   239  }
   240  
   241  func (bw *BytePack) WriteInt64(i int64) {
   242  	bw.WriteUint64(uint64(i))
   243  }
   244  
   245  func (bw *BytePack) WriteUint64(i uint64) {
   246  	binary.BigEndian.PutUint64(bw.b[bw.off:], i)
   247  	bw.off += SizeofI64
   248  }
   249  
   250  func (bw *BytePack) WriteInt16(i int16) {
   251  	bw.WriteUint16(uint16(i))
   252  }
   253  
   254  func (bw *BytePack) WriteUint16(i uint16) {
   255  	binary.BigEndian.PutUint16(bw.b[bw.off:], i)
   256  	bw.off += SizeofI16
   257  }
   258  
   259  func (bw *BytePack) WriteInt32(i int32) {
   260  	bw.WriteUint32(uint32(i))
   261  }
   262  
   263  func (bw *BytePack) WriteUint32(i uint32) {
   264  	binary.BigEndian.PutUint32(bw.b[bw.off:], i)
   265  	bw.off += SizeofI32
   266  }
   267  
   268  func (bw *BytePack) WriteBytes(b []byte) {
   269  	bw.WriteString(UnsafeS(b))
   270  }
   271  
   272  func (bw *BytePack) WriteString(s string) {
   273  	l := len(s)
   274  	bw.WriteUint32(uint32(l))
   275  	if l == 0 {
   276  		return
   277  	}
   278  	written := copy(bw.b[bw.off:], s)
   279  	debug.Assert(written == l)
   280  	bw.off += l
   281  }
   282  
   283  func (bw *BytePack) WriteMapStrUint16(mp MapStrUint16) {
   284  	l := int32(len(mp))
   285  	bw.WriteInt32(l)
   286  	if l == 0 {
   287  		return
   288  	}
   289  	for k, v := range mp {
   290  		bw.WriteString(k)
   291  		bw.WriteUint16(v)
   292  	}
   293  }
   294  
   295  func (bw *BytePack) WriteAny(st Packer) {
   296  	prev := bw.off
   297  	st.Pack(bw)
   298  	debug.Assertf(
   299  		bw.off-prev == st.PackedSize(),
   300  		"%T declared %d, saved %d: %+v", st, st.PackedSize(), bw.off-prev, st,
   301  	)
   302  }
   303  
   304  func (bw *BytePack) Bytes() []byte {
   305  	return bw.b[:bw.off]
   306  }