decred.org/dcrdex@v1.0.5/dex/encode/encode.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package encode
     5  
     6  import (
     7  	"crypto/rand"
     8  	"crypto/sha256"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"math"
    13  	"os"
    14  	"time"
    15  )
    16  
    17  var (
    18  	// IntCoder is the DEX-wide integer byte-encoding order. IntCoder must be
    19  	// BigEndian so that variable length data encodings work as intended.
    20  	IntCoder = binary.BigEndian
    21  	// ByteFalse is a byte-slice representation of boolean false.
    22  	ByteFalse = []byte{0}
    23  	// ByteTrue is a byte-slice representation of boolean true.
    24  	ByteTrue = []byte{1}
    25  	// MaxDataLen is the largest byte slice that can be stored when using
    26  	// (BuildyBytes).AddData.
    27  	MaxDataLen = 0x00fe_ffff // top two bytes in big endian stop at 254, signalling 32-bit len
    28  )
    29  
    30  const maxU16 = int(^uint16(0))
    31  
    32  // Uint64Bytes converts the uint16 to a length-2, big-endian encoded byte slice.
    33  func Uint16Bytes(i uint16) []byte {
    34  	b := make([]byte, 2)
    35  	IntCoder.PutUint16(b, i)
    36  	return b
    37  }
    38  
    39  // Uint64Bytes converts the uint32 to a length-4, big-endian encoded byte slice.
    40  func Uint32Bytes(i uint32) []byte {
    41  	b := make([]byte, 4)
    42  	IntCoder.PutUint32(b, i)
    43  	return b
    44  }
    45  
    46  // BytesToUint32 converts the length-4, big-endian encoded byte slice to a uint32.
    47  func BytesToUint32(i []byte) uint32 {
    48  	return IntCoder.Uint32(i[:4])
    49  }
    50  
    51  // Uint64Bytes converts the uint64 to a length-8, big-endian encoded byte slice.
    52  func Uint64Bytes(i uint64) []byte {
    53  	b := make([]byte, 8)
    54  	IntCoder.PutUint64(b, i)
    55  	return b
    56  }
    57  
    58  // CopySlice makes a copy of the slice.
    59  func CopySlice(b []byte) []byte {
    60  	newB := make([]byte, len(b))
    61  	copy(newB, b)
    62  	return newB
    63  }
    64  
    65  // RandomBytes returns a byte slice with the specified length of random bytes.
    66  func RandomBytes(len int) []byte {
    67  	bytes := make([]byte, len)
    68  	_, err := rand.Read(bytes)
    69  	if err != nil {
    70  		panic("error reading random bytes: " + err.Error())
    71  	}
    72  	return bytes
    73  }
    74  
    75  // ClearBytes zeroes the byte slice.
    76  func ClearBytes(b []byte) {
    77  	for i := range b {
    78  		b[i] = 0
    79  	}
    80  }
    81  
    82  // DropMilliseconds returns the time truncated to the previous second.
    83  func DropMilliseconds(t time.Time) time.Time {
    84  	return t.Truncate(time.Second)
    85  }
    86  
    87  // DecodeUTime interprets bytes as a uint64 millisecond Unix timestamp and
    88  // creates a time.Time.
    89  func DecodeUTime(b []byte) time.Time {
    90  	return time.UnixMilli(int64(IntCoder.Uint64(b)))
    91  }
    92  
    93  // ExtractPushes parses the linearly-encoded 2D byte slice into a slice of
    94  // slices. Empty pushes are nil slices.
    95  func ExtractPushes(b []byte, preAlloc ...int) ([][]byte, error) {
    96  	allocPushes := 2
    97  	if len(preAlloc) > 0 {
    98  		allocPushes = preAlloc[0]
    99  	}
   100  	pushes := make([][]byte, 0, allocPushes)
   101  	for {
   102  		if len(b) == 0 {
   103  			break
   104  		}
   105  		l := int(b[0])
   106  		b = b[1:]
   107  		if l == 255 {
   108  			if len(b) < 2 {
   109  				return nil, fmt.Errorf("2 bytes not available for data length")
   110  			}
   111  			l = int(IntCoder.Uint16(b[:2]))
   112  			if l < 255 {
   113  				// This indicates it's really a uint32 capped at 0x00fe_ffff, and
   114  				// we are looking at the top two bytes. Decode all four.
   115  				if len(b) < 4 {
   116  					return nil, fmt.Errorf("4 bytes not available for 32-bit data length")
   117  				}
   118  				l = int(IntCoder.Uint32(b[:4]))
   119  				b = b[4:]
   120  			} else { // includes 255
   121  				b = b[2:]
   122  			}
   123  		}
   124  		if len(b) < l {
   125  			return nil, fmt.Errorf("data too short for pop of %d bytes", l)
   126  		}
   127  		if l == 0 {
   128  			// If data length is zero, append nil instead of an empty slice.
   129  			pushes = append(pushes, nil)
   130  			continue
   131  		}
   132  		pushes = append(pushes, b[:l])
   133  		b = b[l:]
   134  	}
   135  	return pushes, nil
   136  }
   137  
   138  // DecodeBlob decodes a versioned blob into its version and the pushes extracted
   139  // from its data. Empty pushes will be nil.
   140  func DecodeBlob(b []byte, preAlloc ...int) (byte, [][]byte, error) {
   141  	if len(b) == 0 {
   142  		return 0, nil, fmt.Errorf("zero length blob not allowed")
   143  	}
   144  	ver := b[0]
   145  	b = b[1:]
   146  	pushes, err := ExtractPushes(b, preAlloc...)
   147  	return ver, pushes, err
   148  }
   149  
   150  // BuildyBytes is a byte-slice with an AddData method for building linearly
   151  // encoded 2D byte slices. The AddData method supports chaining. The canonical
   152  // use case is to create "versioned blobs", where the BuildyBytes is
   153  // instantiated with a single version byte, and then data pushes are added using
   154  // the AddData method. Example use:
   155  //
   156  //	version := 0
   157  //	b := BuildyBytes{version}.AddData(data1).AddData(data2)
   158  //
   159  // The versioned blob can be decoded with DecodeBlob to separate the version
   160  // byte and the "payload". BuildyBytes has some similarities to dcrd's
   161  // txscript.ScriptBuilder, though simpler and less efficient.
   162  type BuildyBytes []byte
   163  
   164  // AddData adds the data to the BuildyBytes, and returns the new BuildyBytes.
   165  // The data has hard-coded length limit of MaxDataLen = 16711679 bytes. The
   166  // caller should ensure the data is not larger since AddData panics if it is.
   167  func (b BuildyBytes) AddData(d []byte) BuildyBytes {
   168  	l := len(d)
   169  	var lBytes []byte
   170  	if l >= 0xff {
   171  		if l > MaxDataLen {
   172  			panic("cannot use addData for pushes > 16711679 bytes")
   173  		}
   174  		var i []byte
   175  		if l > math.MaxUint16 { // not >= since that is historically in 2 bytes
   176  			// We are retrofitting for data longer than 65535 bytes, so we
   177  			// cannot switch to uint32 at 65535 itself since it is possible
   178  			// there is data of exactly that length already stored using just
   179  			// two bytes to encode the length. Thus, the decoder should inspect
   180  			// the top two bytes (big endian), switching to uint32 if under 255.
   181  			// Therefore, the highest length with this scheme is 0x00fe_ffff
   182  			// (16,711,679 bytes).
   183  			i = make([]byte, 4)
   184  			IntCoder.PutUint32(i, uint32(l))
   185  		} else { // includes MaxUint16 for historical reasons
   186  			i = make([]byte, 2)
   187  			IntCoder.PutUint16(i, uint16(l))
   188  		}
   189  		lBytes = append([]byte{0xff}, i...)
   190  	} else {
   191  		lBytes = []byte{byte(l)}
   192  	}
   193  	return append(b, append(lBytes, d...)...)
   194  }
   195  
   196  // FileHash generates the SHA256 hash of the specified file.
   197  func FileHash(name string) ([]byte, error) {
   198  	f, err := os.Open(name)
   199  	if err != nil {
   200  		return nil, fmt.Errorf("error opening file %s for hashing: %w", name, err)
   201  	}
   202  	defer f.Close()
   203  	h := sha256.New()
   204  	if _, err := io.Copy(h, f); err != nil {
   205  		return nil, fmt.Errorf("error copying file %s for hashing: %w", name, err)
   206  	}
   207  	return h.Sum(nil), nil
   208  }