parser: generalize API for decoding serializable types
This commit is contained in:
parent
c947b00d36
commit
35638b3900
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/binary"
|
||||
"log"
|
||||
|
||||
"github.com/gtank/ctxd/parser/internal/bytestring"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -80,12 +81,37 @@ func (hdr *rawBlockHeader) UnmarshalBinary(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type blockHeaderDecoder struct {
|
||||
in *bytestring.String
|
||||
}
|
||||
|
||||
func NewBlockHeaderDecoder(in *bytestring.String) Decoder {
|
||||
return &blockHeaderDecoder{in}
|
||||
}
|
||||
|
||||
func (dec *blockHeaderDecoder) Decode(out Serializable) error {
|
||||
hdr, ok := out.(*BlockHeader)
|
||||
if !ok {
|
||||
return errors.New("unexpected Serializable for BlockHeader decoder")
|
||||
}
|
||||
|
||||
if hdr.rawBlockHeader == nil {
|
||||
hdr.rawBlockHeader = new(rawBlockHeader)
|
||||
}
|
||||
|
||||
err := binary.Read(dec.in, binary.LittleEndian, hdr.rawBlockHeader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parsing block header")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BlockHeader struct {
|
||||
*rawBlockHeader
|
||||
cachedHash []byte
|
||||
}
|
||||
|
||||
func (hdr *BlockHeader) GetBlockHeaderHash() []byte {
|
||||
func (hdr *BlockHeader) GetHash() []byte {
|
||||
if hdr.cachedHash != nil {
|
||||
return hdr.cachedHash
|
||||
}
|
||||
|
@ -103,16 +129,3 @@ func (hdr *BlockHeader) GetBlockHeaderHash() []byte {
|
|||
hdr.cachedHash = digest[:]
|
||||
return hdr.cachedHash
|
||||
}
|
||||
|
||||
func (hdr *BlockHeader) GetSerializedSize() int {
|
||||
// TODO: Make this dynamic. Low priority; it's unlikely to change.
|
||||
return SER_BLOCK_HEADER_SIZE
|
||||
}
|
||||
|
||||
func ReadBlockHeader(blockHeader *BlockHeader, data []byte) error {
|
||||
if blockHeader.rawBlockHeader == nil {
|
||||
blockHeader.rawBlockHeader = new(rawBlockHeader)
|
||||
}
|
||||
return blockHeader.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gtank/ctxd/parser/internal/bytestring"
|
||||
)
|
||||
|
||||
func TestBlockHeader(t *testing.T) {
|
||||
|
@ -26,14 +28,21 @@ func TestBlockHeader(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Try to read the header
|
||||
blockHeader := &BlockHeader{}
|
||||
err = ReadBlockHeader(blockHeader, decodedBlockData)
|
||||
s := bytestring.String(decodedBlockData)
|
||||
startLength := len(s)
|
||||
dec := NewBlockHeaderDecoder(&s)
|
||||
|
||||
blockHeader := BlockHeader{}
|
||||
err = dec.Decode(&blockHeader)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if (startLength - len(s)) != SER_BLOCK_HEADER_SIZE {
|
||||
t.Error("did not advance underlying bytestring")
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if blockHeader.Version != 4 {
|
||||
t.Error("Read wrong version in a test block.")
|
||||
|
@ -73,7 +82,7 @@ func TestBlockHeader(t *testing.T) {
|
|||
break
|
||||
}
|
||||
|
||||
hash := blockHeader.GetBlockHeaderHash()
|
||||
hash := blockHeader.GetHash()
|
||||
|
||||
// This is not necessarily true for anything but our current test cases.
|
||||
for _, b := range hash[28:] {
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
// Package bytestring provides a cryptobyte-inspired API specialized to the
|
||||
// needs of parsing Zcash transactions.
|
||||
package bytestring
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const MAX_COMPACT_SIZE uint64 = 0x02000000
|
||||
|
||||
// String represents a string of bytes and provides methods for parsing values
|
||||
// from it.
|
||||
type String []byte
|
||||
|
||||
// read advances the string by n bytes and returns them. If fewer than n bytes
|
||||
// remain, it returns nil.
|
||||
func (s *String) read(n int) []byte {
|
||||
if len(*s) < n {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := (*s)[:n]
|
||||
(*s) = (*s)[n:]
|
||||
return out
|
||||
}
|
||||
|
||||
// Read reads the next len(p) bytes from the string, or the remainder of the
|
||||
// string if len(*s) < len(p). It returns the number of bytes read as n. If the
|
||||
// string is empty it returns an io.EOF error, or a nil error if len(p) == 0.
|
||||
// Read satisfies io.Reader.
|
||||
func (s *String) Read(p []byte) (n int, err error) {
|
||||
if s.Empty() {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n = copy(p, *s)
|
||||
if !s.Skip(n) {
|
||||
return 0, errors.New("unexpected end of bytestring read")
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Empty reports whether or not the string is empty.
|
||||
func (s *String) Empty() bool {
|
||||
return len(*s) == 0
|
||||
}
|
||||
|
||||
// Skip advances the string by n bytes and reports whether it was successful.
|
||||
func (s *String) Skip(n int) bool {
|
||||
return s.read(n) != nil
|
||||
}
|
||||
|
||||
// ReadBytes reads n bytes into out and advances over them. It reports if the
|
||||
// read was successful.
|
||||
func (s *String) ReadBytes(out *[]byte, n int) bool {
|
||||
v := s.read(n)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = v
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadCompactSize reads and interprets a Bitcoin-custom compact integer
|
||||
// encoding used for length-prefixing and count values. If the values fall
|
||||
// outside the expected canonical ranges, it returns false.
|
||||
func (s *String) ReadCompactSize(size *uint64) bool {
|
||||
lenBytes := s.read(1)
|
||||
if lenBytes == nil {
|
||||
return false
|
||||
}
|
||||
lenByte := lenBytes[0]
|
||||
|
||||
var lenLen int
|
||||
var length, minSize uint64
|
||||
|
||||
switch {
|
||||
case lenByte < 253:
|
||||
length = uint64(lenByte)
|
||||
case lenByte == 253:
|
||||
lenLen = 2
|
||||
minSize = 253
|
||||
case lenByte == 254:
|
||||
lenLen = 4
|
||||
minSize = 0x10000
|
||||
case lenByte == 255:
|
||||
lenLen = 8
|
||||
minSize = 0x100000000
|
||||
}
|
||||
|
||||
if lenLen > 0 {
|
||||
// expect little endian uint of varying size
|
||||
lenBytes := s.read(lenLen)
|
||||
for i := lenLen - 1; i >= 0; i-- {
|
||||
length <<= 8
|
||||
length = length | uint64(lenBytes[i])
|
||||
}
|
||||
}
|
||||
|
||||
if length > MAX_COMPACT_SIZE || length < minSize {
|
||||
return false
|
||||
}
|
||||
|
||||
*size = length
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadCompactLengthPrefixed reads data prefixed by a CompactSize-encoded
|
||||
// length field into out. It reports whether the read was successful.
|
||||
func (s *String) ReadCompactLengthPrefixed(out *String) bool {
|
||||
var length uint64
|
||||
if ok := s.ReadCompactSize(&length); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
v := s.read(int(length))
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
*out = v
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint32 decodes a little-endian, 32-bit value into out and advances over
|
||||
// it. It reports whether the read was successful.
|
||||
func (s *String) ReadUint32(out *uint32) bool {
|
||||
v := s.read(4)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint32(v[0]) | uint32(v[1])<<8 | uint32(v[2])<<16 | uint32(v[3])<<24
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint64 decodes a little-endian, 64-bit value into out and advances over
|
||||
// it. It reports whether the read was successful.
|
||||
func (s *String) ReadUint64(out *uint64) bool {
|
||||
v := s.read(8)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0]) | uint64(v[1])<<8 | uint64(v[2])<<16 | uint64(v[3])<<24 |
|
||||
uint64(v[4])<<32 | uint64(v[5])<<40 | uint64(v[6])<<48 | uint64(v[7])<<56
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package parser
|
||||
|
||||
import "encoding"
|
||||
|
||||
type Serializable interface {
|
||||
encoding.BinaryMarshaler
|
||||
encoding.BinaryUnmarshaler
|
||||
}
|
||||
|
||||
type Decoder interface {
|
||||
Decode(v Serializable) error
|
||||
}
|
Loading…
Reference in New Issue