207 lines
4.7 KiB
Go
207 lines
4.7 KiB
Go
package opentimestamps
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
)
|
|
|
|
// serializationContext helps encoding values in the ots format
|
|
type serializationContext struct {
|
|
w io.Writer
|
|
}
|
|
|
|
// newSerializationContext returns a serializationContext for a writer
|
|
func newSerializationContext(w io.Writer) *serializationContext {
|
|
return &serializationContext{w}
|
|
}
|
|
|
|
// writeBytes writes the raw bytes to the underlying writer
|
|
func (s serializationContext) writeBytes(b []byte) error {
|
|
// number of bytes can be ignored
|
|
// if it is equal len(b) then err is nil
|
|
_, err := s.w.Write(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeByte writes a single byte
|
|
func (s serializationContext) writeByte(b byte) error {
|
|
return s.writeBytes([]byte{b})
|
|
}
|
|
|
|
// writeBool encodes and writes a boolean value
|
|
func (s serializationContext) writeBool(b bool) error {
|
|
if b {
|
|
return s.writeByte(0xff)
|
|
} else {
|
|
return s.writeByte(0x00)
|
|
}
|
|
}
|
|
|
|
// writeVarUint encodes and writes writes a variable-length integer
|
|
func (s serializationContext) writeVarUint(v uint64) error {
|
|
if v == 0 {
|
|
s.writeByte(0x00)
|
|
}
|
|
for v > 0 {
|
|
b := byte(v & 0x7f)
|
|
if v > uint64(0x7f) {
|
|
b |= 0x80
|
|
}
|
|
if err := s.writeByte(b); err != nil {
|
|
return err
|
|
}
|
|
if v <= 0x7f {
|
|
break
|
|
}
|
|
v >>= 7
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeVarBytes encodes and writes a variable-length array
|
|
func (s serializationContext) writeVarBytes(arr []byte) error {
|
|
if err := s.writeVarUint(uint64(len(arr))); err != nil {
|
|
return err
|
|
}
|
|
return s.writeBytes(arr)
|
|
}
|
|
|
|
// deserializationContext helps decoding values from the ots format
|
|
type deserializationContext struct {
|
|
r io.Reader
|
|
}
|
|
|
|
// safety boundary for readBytes
|
|
// allocation limit for arrays
|
|
const maxReadSize = (1 << 12)
|
|
|
|
func (d deserializationContext) dump() string {
|
|
arr, _ := d.r.(*bufio.Reader).Peek(512)
|
|
return fmt.Sprintf("% x", arr)
|
|
}
|
|
|
|
// readBytes reads n bytes.
|
|
func (d deserializationContext) readBytes(n int) ([]byte, error) {
|
|
if n > maxReadSize {
|
|
return nil, fmt.Errorf("over maxReadSize: %d", maxReadSize)
|
|
}
|
|
b := make([]byte, n)
|
|
m, err := d.r.Read(b)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
if n != m {
|
|
return b, fmt.Errorf("expected %d bytes, got %d", n, m)
|
|
}
|
|
return b[:], nil
|
|
}
|
|
|
|
// readByte reads a single byte.
|
|
func (d deserializationContext) readByte() (byte, error) {
|
|
arr, err := d.readBytes(1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return arr[0], nil
|
|
}
|
|
|
|
// readBool reads a boolean.
|
|
func (d deserializationContext) readBool() (bool, error) {
|
|
arr, err := d.readBytes(1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch v := arr[0]; v {
|
|
case 0x00:
|
|
return false, nil
|
|
case 0xff:
|
|
return true, nil
|
|
default:
|
|
return false, fmt.Errorf("unexpected value %x", v)
|
|
}
|
|
}
|
|
|
|
// readVarUint reads a variable-length uint64.
|
|
func (d deserializationContext) readVarUint() (uint64, error) {
|
|
// NOTE
|
|
// the original python implementation has no uint64 limit, but I
|
|
// don't think we'll ever need more that that.
|
|
val := uint64(0)
|
|
shift := uint(0)
|
|
for {
|
|
b, err := d.readByte()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
shifted := uint64(b&0x7f) << shift
|
|
// ghetto overflow check
|
|
if (shifted >> shift) != uint64(b&0x7f) {
|
|
return 0, fmt.Errorf("uint64 overflow")
|
|
}
|
|
val |= shifted
|
|
if b&0x80 == 0 {
|
|
return val, nil
|
|
}
|
|
shift += 7
|
|
}
|
|
}
|
|
|
|
// readVarBytes reads variable-length number of bytes.
|
|
func (d deserializationContext) readVarBytes(minLen, maxLen int) ([]byte, error) {
|
|
v, err := d.readVarUint()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v > math.MaxInt32 {
|
|
return nil, fmt.Errorf("int overflow")
|
|
}
|
|
vint := int(v)
|
|
if maxLen < vint || vint < minLen {
|
|
return nil, fmt.Errorf(
|
|
"varbytes length %d outside range (%d, %d)",
|
|
vint, minLen, maxLen,
|
|
)
|
|
}
|
|
|
|
return d.readBytes(vint)
|
|
}
|
|
|
|
// assertMagic removes reads the expected bytes from the stream. Returns an
|
|
// error if the bytes are unexpected.
|
|
func (d deserializationContext) assertMagic(expected []byte) error {
|
|
arr, err := d.readBytes(len(expected))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !bytes.Equal(expected, arr) {
|
|
return fmt.Errorf(
|
|
"magic bytes mismatch, expected % x got % x",
|
|
expected, arr,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// assertEOF reads a byte and returns true if the end of the reader is reached.
|
|
// Careful: the read operation is a side-effect.
|
|
func (d deserializationContext) assertEOF() bool {
|
|
// Unfortunately we can't always do a zero-byte read here, since some
|
|
// reader implementations fail to return EOF. This means assertEOF
|
|
_, err := d.readByte()
|
|
return err == io.EOF
|
|
}
|
|
|
|
// newDeserializationContext returns a deserializationContext for a reader
|
|
func newDeserializationContext(r io.Reader) *deserializationContext {
|
|
// TODO
|
|
// bufio is used here to allow debugging via d.dump()
|
|
// once this code here is robust enough we can just pass r
|
|
return &deserializationContext{bufio.NewReader(r)}
|
|
}
|