+++ /dev/null
-// Copyright (c) 2013-2016 The btcsuite developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package wire
-
-import (
- "crypto/rand"
- "encoding/binary"
- "fmt"
- "io"
- "math"
- "time"
-
- "github.com/btcsuite/btcd/chaincfg/chainhash"
-)
-
-const (
- // MaxVarIntPayload is the maximum payload size for a variable length integer.
- MaxVarIntPayload = 9
-
- // binaryFreeListMaxItems is the number of buffers to keep in the free
- // list to use for binary serialization and deserialization.
- binaryFreeListMaxItems = 1024
-)
-
-var (
- // littleEndian is a convenience variable since binary.LittleEndian is
- // quite long.
- littleEndian = binary.LittleEndian
-
- // bigEndian is a convenience variable since binary.BigEndian is quite
- // long.
- bigEndian = binary.BigEndian
-)
-
-// binaryFreeList defines a concurrent safe free list of byte slices (up to the
-// maximum number defined by the binaryFreeListMaxItems constant) that have a
-// cap of 8 (thus it supports up to a uint64). It is used to provide temporary
-// buffers for serializing and deserializing primitive numbers to and from their
-// binary encoding in order to greatly reduce the number of allocations
-// required.
-//
-// For convenience, functions are provided for each of the primitive unsigned
-// integers that automatically obtain a buffer from the free list, perform the
-// necessary binary conversion, read from or write to the given io.Reader or
-// io.Writer, and return the buffer to the free list.
-type binaryFreeList chan []byte
-
-// Borrow returns a byte slice from the free list with a length of 8. A new
-// buffer is allocated if there are not any available on the free list.
-func (l binaryFreeList) Borrow() []byte {
- var buf []byte
- select {
- case buf = <-l:
- default:
- buf = make([]byte, 8)
- }
- return buf[:8]
-}
-
-// Return puts the provided byte slice back on the free list. The buffer MUST
-// have been obtained via the Borrow function and therefore have a cap of 8.
-func (l binaryFreeList) Return(buf []byte) {
- select {
- case l <- buf:
- default:
- // Let it go to the garbage collector.
- }
-}
-
-// Uint8 reads a single byte from the provided reader using a buffer from the
-// free list and returns it as a uint8.
-func (l binaryFreeList) Uint8(r io.Reader) (uint8, error) {
- buf := l.Borrow()[:1]
- if _, err := io.ReadFull(r, buf); err != nil {
- l.Return(buf)
- return 0, err
- }
- rv := buf[0]
- l.Return(buf)
- return rv, nil
-}
-
-// Uint16 reads two bytes from the provided reader using a buffer from the
-// free list, converts it to a number using the provided byte order, and returns
-// the resulting uint16.
-func (l binaryFreeList) Uint16(r io.Reader, byteOrder binary.ByteOrder) (uint16, error) {
- buf := l.Borrow()[:2]
- if _, err := io.ReadFull(r, buf); err != nil {
- l.Return(buf)
- return 0, err
- }
- rv := byteOrder.Uint16(buf)
- l.Return(buf)
- return rv, nil
-}
-
-// Uint32 reads four bytes from the provided reader using a buffer from the
-// free list, converts it to a number using the provided byte order, and returns
-// the resulting uint32.
-func (l binaryFreeList) Uint32(r io.Reader, byteOrder binary.ByteOrder) (uint32, error) {
- buf := l.Borrow()[:4]
- if _, err := io.ReadFull(r, buf); err != nil {
- l.Return(buf)
- return 0, err
- }
- rv := byteOrder.Uint32(buf)
- l.Return(buf)
- return rv, nil
-}
-
-// Uint64 reads eight bytes from the provided reader using a buffer from the
-// free list, converts it to a number using the provided byte order, and returns
-// the resulting uint64.
-func (l binaryFreeList) Uint64(r io.Reader, byteOrder binary.ByteOrder) (uint64, error) {
- buf := l.Borrow()[:8]
- if _, err := io.ReadFull(r, buf); err != nil {
- l.Return(buf)
- return 0, err
- }
- rv := byteOrder.Uint64(buf)
- l.Return(buf)
- return rv, nil
-}
-
-// PutUint8 copies the provided uint8 into a buffer from the free list and
-// writes the resulting byte to the given writer.
-func (l binaryFreeList) PutUint8(w io.Writer, val uint8) error {
- buf := l.Borrow()[:1]
- buf[0] = val
- _, err := w.Write(buf)
- l.Return(buf)
- return err
-}
-
-// PutUint16 serializes the provided uint16 using the given byte order into a
-// buffer from the free list and writes the resulting two bytes to the given
-// writer.
-func (l binaryFreeList) PutUint16(w io.Writer, byteOrder binary.ByteOrder, val uint16) error {
- buf := l.Borrow()[:2]
- byteOrder.PutUint16(buf, val)
- _, err := w.Write(buf)
- l.Return(buf)
- return err
-}
-
-// PutUint32 serializes the provided uint32 using the given byte order into a
-// buffer from the free list and writes the resulting four bytes to the given
-// writer.
-func (l binaryFreeList) PutUint32(w io.Writer, byteOrder binary.ByteOrder, val uint32) error {
- buf := l.Borrow()[:4]
- byteOrder.PutUint32(buf, val)
- _, err := w.Write(buf)
- l.Return(buf)
- return err
-}
-
-// PutUint64 serializes the provided uint64 using the given byte order into a
-// buffer from the free list and writes the resulting eight bytes to the given
-// writer.
-func (l binaryFreeList) PutUint64(w io.Writer, byteOrder binary.ByteOrder, val uint64) error {
- buf := l.Borrow()[:8]
- byteOrder.PutUint64(buf, val)
- _, err := w.Write(buf)
- l.Return(buf)
- return err
-}
-
-// binarySerializer provides a free list of buffers to use for serializing and
-// deserializing primitive integer values to and from io.Readers and io.Writers.
-var binarySerializer binaryFreeList = make(chan []byte, binaryFreeListMaxItems)
-
-// errNonCanonicalVarInt is the common format string used for non-canonically
-// encoded variable length integer errors.
-var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " +
- "encode a value greater than %x"
-
-// uint32Time represents a unix timestamp encoded with a uint32. It is used as
-// a way to signal the readElement function how to decode a timestamp into a Go
-// time.Time since it is otherwise ambiguous.
-type uint32Time time.Time
-
-// int64Time represents a unix timestamp encoded with an int64. It is used as
-// a way to signal the readElement function how to decode a timestamp into a Go
-// time.Time since it is otherwise ambiguous.
-type int64Time time.Time
-
-// readElement reads the next sequence of bytes from r using little endian
-// depending on the concrete type of element pointed to.
-func readElement(r io.Reader, element interface{}) error {
- // Attempt to read the element based on the concrete type via fast
- // type assertions first.
- switch e := element.(type) {
- case *int32:
- rv, err := binarySerializer.Uint32(r, littleEndian)
- if err != nil {
- return err
- }
- *e = int32(rv)
- return nil
-
- case *uint32:
- rv, err := binarySerializer.Uint32(r, littleEndian)
- if err != nil {
- return err
- }
- *e = rv
- return nil
-
- case *int64:
- rv, err := binarySerializer.Uint64(r, littleEndian)
- if err != nil {
- return err
- }
- *e = int64(rv)
- return nil
-
- case *uint64:
- rv, err := binarySerializer.Uint64(r, littleEndian)
- if err != nil {
- return err
- }
- *e = rv
- return nil
-
- case *bool:
- rv, err := binarySerializer.Uint8(r)
- if err != nil {
- return err
- }
- if rv == 0x00 {
- *e = false
- } else {
- *e = true
- }
- return nil
-
- // Unix timestamp encoded as a uint32.
- case *uint32Time:
- rv, err := binarySerializer.Uint32(r, binary.LittleEndian)
- if err != nil {
- return err
- }
- *e = uint32Time(time.Unix(int64(rv), 0))
- return nil
-
- // Unix timestamp encoded as an int64.
- case *int64Time:
- rv, err := binarySerializer.Uint64(r, binary.LittleEndian)
- if err != nil {
- return err
- }
- *e = int64Time(time.Unix(int64(rv), 0))
- return nil
-
- // Message header checksum.
- case *[4]byte:
- _, err := io.ReadFull(r, e[:])
- if err != nil {
- return err
- }
- return nil
-
- // Message header command.
- case *[CommandSize]uint8:
- _, err := io.ReadFull(r, e[:])
- if err != nil {
- return err
- }
- return nil
-
- // IP address.
- case *[16]byte:
- _, err := io.ReadFull(r, e[:])
- if err != nil {
- return err
- }
- return nil
-
- case *chainhash.Hash:
- _, err := io.ReadFull(r, e[:])
- if err != nil {
- return err
- }
- return nil
-
- case *ServiceFlag:
- rv, err := binarySerializer.Uint64(r, littleEndian)
- if err != nil {
- return err
- }
- *e = ServiceFlag(rv)
- return nil
-
- case *InvType:
- rv, err := binarySerializer.Uint32(r, littleEndian)
- if err != nil {
- return err
- }
- *e = InvType(rv)
- return nil
-
- case *BitcoinNet:
- rv, err := binarySerializer.Uint32(r, littleEndian)
- if err != nil {
- return err
- }
- *e = BitcoinNet(rv)
- return nil
-
- case *BloomUpdateType:
- rv, err := binarySerializer.Uint8(r)
- if err != nil {
- return err
- }
- *e = BloomUpdateType(rv)
- return nil
-
- case *RejectCode:
- rv, err := binarySerializer.Uint8(r)
- if err != nil {
- return err
- }
- *e = RejectCode(rv)
- return nil
- }
-
- // Fall back to the slower binary.Read if a fast path was not available
- // above.
- return binary.Read(r, littleEndian, element)
-}
-
-// readElements reads multiple items from r. It is equivalent to multiple
-// calls to readElement.
-func readElements(r io.Reader, elements ...interface{}) error {
- for _, element := range elements {
- err := readElement(r, element)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// writeElement writes the little endian representation of element to w.
-func writeElement(w io.Writer, element interface{}) error {
- // Attempt to write the element based on the concrete type via fast
- // type assertions first.
- switch e := element.(type) {
- case int32:
- err := binarySerializer.PutUint32(w, littleEndian, uint32(e))
- if err != nil {
- return err
- }
- return nil
-
- case uint32:
- err := binarySerializer.PutUint32(w, littleEndian, e)
- if err != nil {
- return err
- }
- return nil
-
- case int64:
- err := binarySerializer.PutUint64(w, littleEndian, uint64(e))
- if err != nil {
- return err
- }
- return nil
-
- case uint64:
- err := binarySerializer.PutUint64(w, littleEndian, e)
- if err != nil {
- return err
- }
- return nil
-
- case bool:
- var err error
- if e {
- err = binarySerializer.PutUint8(w, 0x01)
- } else {
- err = binarySerializer.PutUint8(w, 0x00)
- }
- if err != nil {
- return err
- }
- return nil
-
- // Message header checksum.
- case [4]byte:
- _, err := w.Write(e[:])
- if err != nil {
- return err
- }
- return nil
-
- // Message header command.
- case [CommandSize]uint8:
- _, err := w.Write(e[:])
- if err != nil {
- return err
- }
- return nil
-
- // IP address.
- case [16]byte:
- _, err := w.Write(e[:])
- if err != nil {
- return err
- }
- return nil
-
- case *chainhash.Hash:
- _, err := w.Write(e[:])
- if err != nil {
- return err
- }
- return nil
-
- case ServiceFlag:
- err := binarySerializer.PutUint64(w, littleEndian, uint64(e))
- if err != nil {
- return err
- }
- return nil
-
- case InvType:
- err := binarySerializer.PutUint32(w, littleEndian, uint32(e))
- if err != nil {
- return err
- }
- return nil
-
- case BitcoinNet:
- err := binarySerializer.PutUint32(w, littleEndian, uint32(e))
- if err != nil {
- return err
- }
- return nil
-
- case BloomUpdateType:
- err := binarySerializer.PutUint8(w, uint8(e))
- if err != nil {
- return err
- }
- return nil
-
- case RejectCode:
- err := binarySerializer.PutUint8(w, uint8(e))
- if err != nil {
- return err
- }
- return nil
- }
-
- // Fall back to the slower binary.Write if a fast path was not available
- // above.
- return binary.Write(w, littleEndian, element)
-}
-
-// writeElements writes multiple items to w. It is equivalent to multiple
-// calls to writeElement.
-func writeElements(w io.Writer, elements ...interface{}) error {
- for _, element := range elements {
- err := writeElement(w, element)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// ReadVarInt reads a variable length integer from r and returns it as a uint64.
-func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
- discriminant, err := binarySerializer.Uint8(r)
- if err != nil {
- return 0, err
- }
-
- var rv uint64
- switch discriminant {
- case 0xff:
- sv, err := binarySerializer.Uint64(r, littleEndian)
- if err != nil {
- return 0, err
- }
- rv = sv
-
- // The encoding is not canonical if the value could have been
- // encoded using fewer bytes.
- min := uint64(0x100000000)
- if rv < min {
- return 0, messageError("ReadVarInt", fmt.Sprintf(
- errNonCanonicalVarInt, rv, discriminant, min))
- }
-
- case 0xfe:
- sv, err := binarySerializer.Uint32(r, littleEndian)
- if err != nil {
- return 0, err
- }
- rv = uint64(sv)
-
- // The encoding is not canonical if the value could have been
- // encoded using fewer bytes.
- min := uint64(0x10000)
- if rv < min {
- return 0, messageError("ReadVarInt", fmt.Sprintf(
- errNonCanonicalVarInt, rv, discriminant, min))
- }
-
- case 0xfd:
- sv, err := binarySerializer.Uint16(r, littleEndian)
- if err != nil {
- return 0, err
- }
- rv = uint64(sv)
-
- // The encoding is not canonical if the value could have been
- // encoded using fewer bytes.
- min := uint64(0xfd)
- if rv < min {
- return 0, messageError("ReadVarInt", fmt.Sprintf(
- errNonCanonicalVarInt, rv, discriminant, min))
- }
-
- default:
- rv = uint64(discriminant)
- }
-
- return rv, nil
-}
-
-// WriteVarInt serializes val to w using a variable number of bytes depending
-// on its value.
-func WriteVarInt(w io.Writer, pver uint32, val uint64) error {
- if val < 0xfd {
- return binarySerializer.PutUint8(w, uint8(val))
- }
-
- if val <= math.MaxUint16 {
- err := binarySerializer.PutUint8(w, 0xfd)
- if err != nil {
- return err
- }
- return binarySerializer.PutUint16(w, littleEndian, uint16(val))
- }
-
- if val <= math.MaxUint32 {
- err := binarySerializer.PutUint8(w, 0xfe)
- if err != nil {
- return err
- }
- return binarySerializer.PutUint32(w, littleEndian, uint32(val))
- }
-
- err := binarySerializer.PutUint8(w, 0xff)
- if err != nil {
- return err
- }
- return binarySerializer.PutUint64(w, littleEndian, val)
-}
-
-// VarIntSerializeSize returns the number of bytes it would take to serialize
-// val as a variable length integer.
-func VarIntSerializeSize(val uint64) int {
- // The value is small enough to be represented by itself, so it's
- // just 1 byte.
- if val < 0xfd {
- return 1
- }
-
- // Discriminant 1 byte plus 2 bytes for the uint16.
- if val <= math.MaxUint16 {
- return 3
- }
-
- // Discriminant 1 byte plus 4 bytes for the uint32.
- if val <= math.MaxUint32 {
- return 5
- }
-
- // Discriminant 1 byte plus 8 bytes for the uint64.
- return 9
-}
-
-// ReadVarString reads a variable length string from r and returns it as a Go
-// string. A variable length string is encoded as a variable length integer
-// containing the length of the string followed by the bytes that represent the
-// string itself. An error is returned if the length is greater than the
-// maximum block payload size since it helps protect against memory exhaustion
-// attacks and forced panics through malformed messages.
-func ReadVarString(r io.Reader, pver uint32) (string, error) {
- count, err := ReadVarInt(r, pver)
- if err != nil {
- return "", err
- }
-
- // Prevent variable length strings that are larger than the maximum
- // message size. It would be possible to cause memory exhaustion and
- // panics without a sane upper bound on this count.
- if count > MaxMessagePayload {
- str := fmt.Sprintf("variable length string is too long "+
- "[count %d, max %d]", count, MaxMessagePayload)
- return "", messageError("ReadVarString", str)
- }
-
- buf := make([]byte, count)
- _, err = io.ReadFull(r, buf)
- if err != nil {
- return "", err
- }
- return string(buf), nil
-}
-
-// WriteVarString serializes str to w as a variable length integer containing
-// the length of the string followed by the bytes that represent the string
-// itself.
-func WriteVarString(w io.Writer, pver uint32, str string) error {
- err := WriteVarInt(w, pver, uint64(len(str)))
- if err != nil {
- return err
- }
- _, err = w.Write([]byte(str))
- return err
-}
-
-// ReadVarBytes reads a variable length byte array. A byte array is encoded
-// as a varInt containing the length of the array followed by the bytes
-// themselves. An error is returned if the length is greater than the
-// passed maxAllowed parameter which helps protect against memory exhuastion
-// attacks and forced panics thorugh malformed messages. The fieldName
-// parameter is only used for the error message so it provides more context in
-// the error.
-func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
- fieldName string) ([]byte, error) {
-
- count, err := ReadVarInt(r, pver)
- if err != nil {
- return nil, err
- }
-
- // Prevent byte array larger than the max message size. It would
- // be possible to cause memory exhaustion and panics without a sane
- // upper bound on this count.
- if count > uint64(maxAllowed) {
- str := fmt.Sprintf("%s is larger than the max allowed size "+
- "[count %d, max %d]", fieldName, count, maxAllowed)
- return nil, messageError("ReadVarBytes", str)
- }
-
- b := make([]byte, count)
- _, err = io.ReadFull(r, b)
- if err != nil {
- return nil, err
- }
- return b, nil
-}
-
-// WriteVarBytes serializes a variable length byte array to w as a varInt
-// containing the number of bytes, followed by the bytes themselves.
-func WriteVarBytes(w io.Writer, pver uint32, bytes []byte) error {
- slen := uint64(len(bytes))
- err := WriteVarInt(w, pver, slen)
- if err != nil {
- return err
- }
-
- _, err = w.Write(bytes)
- return err
-}
-
-// randomUint64 returns a cryptographically random uint64 value. This
-// unexported version takes a reader primarily to ensure the error paths
-// can be properly tested by passing a fake reader in the tests.
-func randomUint64(r io.Reader) (uint64, error) {
- rv, err := binarySerializer.Uint64(r, bigEndian)
- if err != nil {
- return 0, err
- }
- return rv, nil
-}
-
-// RandomUint64 returns a cryptographically random uint64 value.
-func RandomUint64() (uint64, error) {
- return randomUint64(rand.Reader)
-}