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 }