github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/io/binaryWriter.go (about)

     1  package io
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"io"
     7  	"reflect"
     8  )
     9  
    10  // BinWriter is a convenient wrapper around an io.Writer and err object.
    11  // Used to simplify error handling when writing into an io.Writer
    12  // from a struct with many fields.
    13  type BinWriter struct {
    14  	w   io.Writer
    15  	Err error
    16  	uv  [9]byte
    17  }
    18  
    19  // NewBinWriterFromIO makes a BinWriter from io.Writer.
    20  func NewBinWriterFromIO(iow io.Writer) *BinWriter {
    21  	return &BinWriter{w: iow}
    22  }
    23  
    24  // WriteU64LE writes a uint64 value into the underlying io.Writer in
    25  // little-endian format.
    26  func (w *BinWriter) WriteU64LE(u64 uint64) {
    27  	binary.LittleEndian.PutUint64(w.uv[:8], u64)
    28  	w.WriteBytes(w.uv[:8])
    29  }
    30  
    31  // WriteU32LE writes a uint32 value into the underlying io.Writer in
    32  // little-endian format.
    33  func (w *BinWriter) WriteU32LE(u32 uint32) {
    34  	binary.LittleEndian.PutUint32(w.uv[:4], u32)
    35  	w.WriteBytes(w.uv[:4])
    36  }
    37  
    38  // WriteU16LE writes a uint16 value into the underlying io.Writer in
    39  // little-endian format.
    40  func (w *BinWriter) WriteU16LE(u16 uint16) {
    41  	binary.LittleEndian.PutUint16(w.uv[:2], u16)
    42  	w.WriteBytes(w.uv[:2])
    43  }
    44  
    45  // WriteU16BE writes a uint16 value into the underlying io.Writer in
    46  // big-endian format.
    47  func (w *BinWriter) WriteU16BE(u16 uint16) {
    48  	binary.BigEndian.PutUint16(w.uv[:2], u16)
    49  	w.WriteBytes(w.uv[:2])
    50  }
    51  
    52  // WriteB writes a byte into the underlying io.Writer.
    53  func (w *BinWriter) WriteB(u8 byte) {
    54  	w.uv[0] = u8
    55  	w.WriteBytes(w.uv[:1])
    56  }
    57  
    58  // WriteBool writes a boolean value into the underlying io.Writer encoded as
    59  // a byte with values of 0 or 1.
    60  func (w *BinWriter) WriteBool(b bool) {
    61  	var i byte
    62  	if b {
    63  		i = 1
    64  	}
    65  	w.WriteB(i)
    66  }
    67  
    68  // WriteArray writes a slice or an array arr into w. Note that nil slices and
    69  // empty slices are gonna be treated the same resulting in an equal zero-length
    70  // array encoded.
    71  func (w *BinWriter) WriteArray(arr any) {
    72  	switch val := reflect.ValueOf(arr); val.Kind() {
    73  	case reflect.Slice, reflect.Array:
    74  		if w.Err != nil {
    75  			return
    76  		}
    77  
    78  		typ := val.Type().Elem()
    79  
    80  		w.WriteVarUint(uint64(val.Len()))
    81  		for i := 0; i < val.Len(); i++ {
    82  			el, ok := val.Index(i).Interface().(encodable)
    83  			if !ok {
    84  				el, ok = val.Index(i).Addr().Interface().(encodable)
    85  				if !ok {
    86  					panic(typ.String() + " is not encodable")
    87  				}
    88  			}
    89  
    90  			el.EncodeBinary(w)
    91  		}
    92  	default:
    93  		panic("not an array")
    94  	}
    95  }
    96  
    97  // WriteVarUint writes a uint64 into the underlying writer using variable-length encoding.
    98  func (w *BinWriter) WriteVarUint(val uint64) {
    99  	if w.Err != nil {
   100  		return
   101  	}
   102  
   103  	n := PutVarUint(w.uv[:], val)
   104  	w.WriteBytes(w.uv[:n])
   105  }
   106  
   107  // PutVarUint puts a val in the varint form to the pre-allocated buffer.
   108  func PutVarUint(data []byte, val uint64) int {
   109  	_ = data[8]
   110  	if val < 0xfd {
   111  		data[0] = byte(val)
   112  		return 1
   113  	}
   114  	if val < 0xFFFF {
   115  		data[0] = byte(0xfd)
   116  		binary.LittleEndian.PutUint16(data[1:], uint16(val))
   117  		return 3
   118  	}
   119  	if val < 0xFFFFFFFF {
   120  		data[0] = byte(0xfe)
   121  		binary.LittleEndian.PutUint32(data[1:], uint32(val))
   122  		return 5
   123  	}
   124  
   125  	data[0] = byte(0xff)
   126  	binary.LittleEndian.PutUint64(data[1:], val)
   127  	return 9
   128  }
   129  
   130  // WriteBytes writes a variable byte into the underlying io.Writer without prefix.
   131  func (w *BinWriter) WriteBytes(b []byte) {
   132  	if w.Err != nil {
   133  		return
   134  	}
   135  	_, w.Err = w.w.Write(b)
   136  }
   137  
   138  // WriteVarBytes writes a variable length byte array into the underlying io.Writer.
   139  func (w *BinWriter) WriteVarBytes(b []byte) {
   140  	w.WriteVarUint(uint64(len(b)))
   141  	w.WriteBytes(b)
   142  }
   143  
   144  // WriteString writes a variable length string into the underlying io.Writer.
   145  func (w *BinWriter) WriteString(s string) {
   146  	w.WriteVarUint(uint64(len(s)))
   147  	if w.Err != nil {
   148  		return
   149  	}
   150  	_, w.Err = io.WriteString(w.w, s)
   151  }
   152  
   153  // Grow tries to increase the underlying buffer capacity so that at least n bytes
   154  // can be written without reallocation. If the writer is not a buffer, this is a no-op.
   155  func (w *BinWriter) Grow(n int) {
   156  	if b, ok := w.w.(*bytes.Buffer); ok {
   157  		b.Grow(n)
   158  	}
   159  }