| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- package util
- import (
- "bytes"
- "encoding/binary"
- "errors"
- "io"
- "math"
- "unsafe"
- "github.com/opencost/opencost/core/pkg/util/stringutil"
- )
- // NonPrimitiveTypeError represents an error where the user provided a non-primitive data type for reading/writing
- var NonPrimitiveTypeError error = errors.New("Type provided to read/write does not fit inside 8 bytes.")
- // Buffer is a utility type which implements a very basic binary protocol for
- // writing core go types.
- type Buffer struct {
- b *bytes.Buffer
- }
- // NewBuffer creates a new Buffer instance using LittleEndian ByteOrder.
- func NewBuffer() *Buffer {
- var b bytes.Buffer
- return &Buffer{
- b: &b,
- }
- }
- // NewBufferFromBytes creates a new Buffer instance using the provided byte slice.
- // The new buffer assumes ownership of the byte slice.
- func NewBufferFromBytes(b []byte) *Buffer {
- return &Buffer{
- b: bytes.NewBuffer(b),
- }
- }
- // NewBufferFrom creates a new Buffer instance using the remaining unread data from the
- // provided Buffer instance. The new buffer assumes ownership of the underlying data.
- func NewBufferFrom(b *Buffer) *Buffer {
- bb := b.Bytes()
- return &Buffer{
- b: bytes.NewBuffer(bb),
- }
- }
- // WriteBool writes a bool value to the buffer.
- func (b *Buffer) WriteBool(t bool) {
- write(b.b, t)
- }
- // WriteInt writes an int value to the buffer.
- func (b *Buffer) WriteInt(i int) {
- write(b.b, int32(i))
- }
- // WriteInt8 writes an int8 value to the buffer.
- func (b *Buffer) WriteInt8(i int8) {
- write(b.b, i)
- }
- // WriteInt16 writes an int16 value to the buffer.
- func (b *Buffer) WriteInt16(i int16) {
- write(b.b, i)
- }
- // WriteInt32 writes an int32 value to the buffer.
- func (b *Buffer) WriteInt32(i int32) {
- write(b.b, i)
- }
- // WriteInt64 writes an int64 value to the buffer.
- func (b *Buffer) WriteInt64(i int64) {
- write(b.b, i)
- }
- // WriteUInt writes a uint value to the buffer.
- func (b *Buffer) WriteUInt(i uint) {
- write(b.b, i)
- }
- // WriteUInt8 writes a uint8 value to the buffer.
- func (b *Buffer) WriteUInt8(i uint8) {
- write(b.b, i)
- }
- // WriteUInt16 writes a uint16 value to the buffer.
- func (b *Buffer) WriteUInt16(i uint16) {
- write(b.b, i)
- }
- // WriteUInt32 writes a uint32 value to the buffer.
- func (b *Buffer) WriteUInt32(i uint32) {
- write(b.b, i)
- }
- // WriteUInt64 writes a uint64 value to the buffer.
- func (b *Buffer) WriteUInt64(i uint64) {
- write(b.b, i)
- }
- // WriteFloat32 writes a float32 value to the buffer.
- func (b *Buffer) WriteFloat32(i float32) {
- write(b.b, i)
- }
- // WriteFloat64 writes a float64 value to the buffer.
- func (b *Buffer) WriteFloat64(i float64) {
- write(b.b, i)
- }
- // WriteString writes the string's length as a uint16 followed by the string contents.
- func (b *Buffer) WriteString(i string) {
- s := stringToBytes(i)
- // string lengths are limited to uint16 - See ReadString()
- if len(s) > math.MaxUint16 {
- s = s[:math.MaxUint16]
- }
- write(b.b, uint16(len(s)))
- b.b.Write(s)
- }
- // WriteBytes writes the contents of the byte slice to the buffer.
- func (b *Buffer) WriteBytes(bytes []byte) {
- b.b.Write(bytes)
- }
- // ReadBool reads a bool value from the buffer.
- func (b *Buffer) ReadBool() bool {
- var i bool
- read(b.b, &i)
- return i
- }
- // ReadInt reads an int value from the buffer.
- func (b *Buffer) ReadInt() int {
- var i int32
- read(b.b, &i)
- return int(i)
- }
- // ReadInt8 reads an int8 value from the buffer.
- func (b *Buffer) ReadInt8() int8 {
- var i int8
- read(b.b, &i)
- return i
- }
- // ReadInt16 reads an int16 value from the buffer.
- func (b *Buffer) ReadInt16() int16 {
- var i int16
- read(b.b, &i)
- return i
- }
- // ReadInt32 reads an int32 value from the buffer.
- func (b *Buffer) ReadInt32() int32 {
- var i int32
- read(b.b, &i)
- return i
- }
- // ReadInt64 reads an int64 value from the buffer.
- func (b *Buffer) ReadInt64() int64 {
- var i int64
- read(b.b, &i)
- return i
- }
- // ReadUInt reads a uint value from the buffer.
- func (b *Buffer) ReadUInt() uint {
- var i uint
- read(b.b, &i)
- return i
- }
- // ReadUInt8 reads a uint8 value from the buffer.
- func (b *Buffer) ReadUInt8() uint8 {
- var i uint8
- read(b.b, &i)
- return i
- }
- // ReadUInt16 reads a uint16 value from the buffer.
- func (b *Buffer) ReadUInt16() uint16 {
- var i uint16
- read(b.b, &i)
- return i
- }
- // ReadUInt32 reads a uint32 value from the buffer.
- func (b *Buffer) ReadUInt32() uint32 {
- var i uint32
- read(b.b, &i)
- return i
- }
- // ReadUInt64 reads a uint64 value from the buffer.
- func (b *Buffer) ReadUInt64() uint64 {
- var i uint64
- read(b.b, &i)
- return i
- }
- // ReadFloat32 reads a float32 value from the buffer.
- func (b *Buffer) ReadFloat32() float32 {
- var i float32
- read(b.b, &i)
- return i
- }
- // ReadFloat64 reads a float64 value from the buffer.
- func (b *Buffer) ReadFloat64() float64 {
- var i float64
- read(b.b, &i)
- return i
- }
- // ReadString reads a uint16 value from the buffer representing the string's length,
- // then uses the length to extract the exact length []byte representing the string.
- func (b *Buffer) ReadString() string {
- var l uint16
- read(b.b, &l)
- return bytesToString(b.b.Next(int(l)))
- }
- // ReadBytes reads the specified length from the buffer and returns the byte slice.
- func (b *Buffer) ReadBytes(length int) []byte {
- return b.b.Next(length)
- }
- // Bytes returns the unread portion of the underlying buffer storage.
- func (b *Buffer) Bytes() []byte {
- return b.b.Bytes()
- }
- // Read reads structured binary data from r into data.
- func read(r *bytes.Buffer, data interface{}) error {
- order := binary.LittleEndian
- var b [8]byte
- if n := intDataSize(data); n != 0 {
- bs := b[:n]
- if _, err := readFull(r, bs); err != nil {
- return err
- }
- switch data := data.(type) {
- case *bool:
- *data = bs[0] != 0
- case *int8:
- *data = int8(bs[0])
- case *uint8:
- *data = bs[0]
- case *int16:
- *data = int16(order.Uint16(bs))
- case *uint16:
- *data = order.Uint16(bs)
- case *int32:
- *data = int32(order.Uint32(bs))
- case *uint32:
- *data = order.Uint32(bs)
- case *int64:
- *data = int64(order.Uint64(bs))
- case *uint64:
- *data = order.Uint64(bs)
- case *float32:
- *data = math.Float32frombits(order.Uint32(bs))
- case *float64:
- *data = math.Float64frombits(order.Uint64(bs))
- default:
- n = 0 // fast path doesn't apply
- }
- if n != 0 {
- return nil
- }
- }
- return NonPrimitiveTypeError
- }
- // read full is a bytes.Buffer specific implementation of ioutil.ReadFull() which
- // avoids escaping our stack allocated scratch bytes
- func readFull(r *bytes.Buffer, buf []byte) (n int, err error) {
- min := len(buf)
- for n < min && err == nil {
- var nn int
- nn, err = r.Read(buf[n:])
- n += nn
- }
- if n >= min {
- err = nil
- } else if n > 0 && err == io.EOF {
- err = io.ErrUnexpectedEOF
- }
- return
- }
- // Write writes the binary representation of data into w.
- func write(w *bytes.Buffer, data interface{}) error {
- order := binary.LittleEndian
- var b [8]byte
- if n := intDataSize(data); n != 0 {
- bs := b[:n]
- switch v := data.(type) {
- case *bool:
- if *v {
- bs[0] = 1
- } else {
- bs[0] = 0
- }
- case bool:
- if v {
- bs[0] = 1
- } else {
- bs[0] = 0
- }
- case *int8:
- bs[0] = byte(*v)
- case int8:
- bs[0] = byte(v)
- case *uint8:
- bs[0] = *v
- case uint8:
- bs[0] = v
- case *int16:
- order.PutUint16(bs, uint16(*v))
- case int16:
- order.PutUint16(bs, uint16(v))
- case *uint16:
- order.PutUint16(bs, *v)
- case uint16:
- order.PutUint16(bs, v)
- case *int32:
- order.PutUint32(bs, uint32(*v))
- case int32:
- order.PutUint32(bs, uint32(v))
- case *uint32:
- order.PutUint32(bs, *v)
- case uint32:
- order.PutUint32(bs, v)
- case *int64:
- order.PutUint64(bs, uint64(*v))
- case int64:
- order.PutUint64(bs, uint64(v))
- case *uint64:
- order.PutUint64(bs, *v)
- case uint64:
- order.PutUint64(bs, v)
- case *float32:
- order.PutUint32(bs, math.Float32bits(*v))
- case float32:
- order.PutUint32(bs, math.Float32bits(v))
- case *float64:
- order.PutUint64(bs, math.Float64bits(*v))
- case float64:
- order.PutUint64(bs, math.Float64bits(v))
- }
- _, err := w.Write(bs)
- return err
- }
- return NonPrimitiveTypeError
- }
- // intDataSize returns the size of the data required to represent the data when encoded.
- // It returns zero if the type cannot be implemented by the fast path in Read or Write.
- func intDataSize(data interface{}) int {
- switch data.(type) {
- case bool, int8, uint8, *bool, *int8, *uint8:
- return 1
- case int16, uint16, *int16, *uint16:
- return 2
- case int32, uint32, *int32, *uint32:
- return 4
- case int64, uint64, *int64, *uint64:
- return 8
- case float32, *float32:
- return 4
- case float64, *float64:
- return 8
- }
- return 0
- }
- // Conversion from byte slice to string
- func bytesToString(b []byte) string {
- // This code will take the passed byte slice and cast it in-place into a string. By doing
- // this, we are pinning the byte slice's underlying array in memory, preventing it from
- // being garbage collected while the string is still in use. If we are using the Bank()
- // functionality to cache new strings, we risk keeping the pinned array alive. To avoid this,
- // we will use the BankFunc() call which uses the casted string to check for existence of a
- // cached string. If it exists, then we drop the pinned reference immediately and use the
- // cached string. If it does _not_ exist, then we use the passed func() string to allocate a new
- // string and cache it. This will prevent us from allocating throw-away strings just to
- // check our cache.
- pinned := unsafe.String(unsafe.SliceData(b), len(b))
- return stringutil.BankFunc(pinned, func() string {
- return string(b)
- })
- }
- // Direct string to byte conversion that doesn't allocate.
- func stringToBytes(s string) []byte {
- return unsafe.Slice(unsafe.StringData(s), len(s))
- }
|