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 }