move gotd fork into repo. (#111)
- update to latest telegram layer - remove some references to fields in tg.Entities that don't exist in the schema - originally added here: https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e - referenced here - https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3 - https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Package bin implements binary serialization and deserialization for TL,
|
||||
// providing Buffer that can decode and encode basic Type Language types.
|
||||
//
|
||||
// This package is not intended to be used directly.
|
||||
//
|
||||
// Ref:
|
||||
// - https://core.telegram.org/mtproto/serialize
|
||||
// - https://core.telegram.org/mtproto/TL-abstract-types
|
||||
package bin
|
||||
|
||||
// Basic TL types.
|
||||
const (
|
||||
TypeIntID = 0xa8509bda // int = Int (0xa8509bda)
|
||||
TypeLongID = 0x22076cba // long = Long (0x22076cba)
|
||||
TypeDoubleID = 0x2210c154 // double = Double (0x2210c154)
|
||||
TypeStringID = 0xb5286e24 // string = String (0xb5286e24)
|
||||
TypeVector = 0x1cb5c415 // vector {t:Type} # [ t ] = Vector t
|
||||
TypeBytes = 0xe937bb82 // bytes#e937bb82 = Bytes
|
||||
|
||||
TypeTrue = 0x997275b5 // boolTrue#997275b5 = Bool
|
||||
TypeFalse = 0xbc799737 // boolFalse#bc799737 = Bool
|
||||
)
|
||||
|
||||
// Word represents 4-byte sequence.
|
||||
// Values in TL are generally aligned to Word.
|
||||
const Word = 4
|
||||
|
||||
func nearestPaddedValueLength(l int) int {
|
||||
n := Word * (l / Word)
|
||||
if n < l {
|
||||
n += Word
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Encoder can encode it's binary form to Buffer.
|
||||
type Encoder interface {
|
||||
Encode(b *Buffer) error
|
||||
}
|
||||
|
||||
// Decoder can decode it's binary form from Buffer.
|
||||
type Decoder interface {
|
||||
Decode(b *Buffer) error
|
||||
}
|
||||
|
||||
// BareEncoder can encode it's binary form to Buffer.
|
||||
// BareEncoder is like Encoder, but encodes object as bare.
|
||||
type BareEncoder interface {
|
||||
EncodeBare(b *Buffer) error
|
||||
}
|
||||
|
||||
// BareDecoder can decode it's binary form from Buffer.
|
||||
// BareEncoder is like Encoder, but decodes object as bare.
|
||||
type BareDecoder interface {
|
||||
DecodeBare(b *Buffer) error
|
||||
}
|
||||
|
||||
// Object wraps Decoder and Encoder interface and represents TL Object.
|
||||
type Object interface {
|
||||
Decoder
|
||||
Encoder
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Buffer implements low level binary (de-)serialization for TL.
|
||||
type Buffer struct {
|
||||
Buf []byte
|
||||
}
|
||||
|
||||
// Encode wrapper.
|
||||
func (b *Buffer) Encode(e Encoder) error {
|
||||
return e.Encode(b)
|
||||
}
|
||||
|
||||
// Decode wrapper.
|
||||
func (b *Buffer) Decode(d Decoder) error {
|
||||
return d.Decode(b)
|
||||
}
|
||||
|
||||
// ResetN resets buffer and expands it to fit n bytes.
|
||||
func (b *Buffer) ResetN(n int) {
|
||||
b.Buf = append(b.Buf[:0], make([]byte, n)...)
|
||||
}
|
||||
|
||||
// Expand expands buffer to add n bytes.
|
||||
func (b *Buffer) Expand(n int) {
|
||||
b.Buf = append(b.Buf, make([]byte, n)...)
|
||||
}
|
||||
|
||||
// Skip moves cursor for next n bytes.
|
||||
func (b *Buffer) Skip(n int) {
|
||||
b.Buf = b.Buf[n:]
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if len(b.Buf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(p, b.Buf)
|
||||
b.Buf = b.Buf[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Copy returns new copy of buffer.
|
||||
func (b *Buffer) Copy() []byte {
|
||||
return append([]byte{}, b.Buf...)
|
||||
}
|
||||
|
||||
// Raw returns internal byte slice.
|
||||
func (b Buffer) Raw() []byte {
|
||||
return b.Buf
|
||||
}
|
||||
|
||||
// Len returns length of internal buffer.
|
||||
func (b Buffer) Len() int {
|
||||
return len(b.Buf)
|
||||
}
|
||||
|
||||
// ResetTo sets internal buffer exactly to provided value.
|
||||
//
|
||||
// Buffer will retain buf, so user should not modify or read it
|
||||
// concurrently.
|
||||
func (b *Buffer) ResetTo(buf []byte) {
|
||||
b.Buf = buf
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
||||
)
|
||||
|
||||
func TestExpandReset(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{}
|
||||
b.PutInt(10)
|
||||
before := b.Len()
|
||||
copyBuf := b.Copy()
|
||||
|
||||
b.Expand(2)
|
||||
a.Equal(before+2, b.Len())
|
||||
a.Equal(copyBuf, b.Buf[:before], "buffer overwrite")
|
||||
|
||||
b.ResetN(b.Len() + 2)
|
||||
a.Zero(b.Buf[0], "buffer not zeroed")
|
||||
|
||||
before = b.Len()
|
||||
b.Skip(2)
|
||||
a.Equal(before-2, b.Len())
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
b := Buffer{}
|
||||
b.PutInt(10)
|
||||
copyBuf := b.Copy()
|
||||
copyBuf[0] = 1
|
||||
require.Equal(t, byte(10), b.Buf[0], "buffer overwritten from copy")
|
||||
}
|
||||
|
||||
func TestBuffer_ResetN(t *testing.T) {
|
||||
var b Buffer
|
||||
testutil.ZeroAlloc(t, func() {
|
||||
b.ResetN(1024)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuffer_Raw(t *testing.T) {
|
||||
b := Buffer{Buf: []byte{1, 2, 3}}
|
||||
b.Raw()[0] = 2
|
||||
require.Equal(t, []byte{2, 2, 3}, b.Buf)
|
||||
}
|
||||
|
||||
func TestBuffer_ResetTo(t *testing.T) {
|
||||
orig := []byte{1, 2, 3}
|
||||
b := Buffer{Buf: orig}
|
||||
|
||||
b.ResetTo([]byte{4, 5, 6})
|
||||
b.Buf[0] = 2
|
||||
|
||||
require.Equal(t, []byte{1, 2, 3}, orig)
|
||||
require.Equal(t, []byte{2, 5, 6}, b.Buf)
|
||||
}
|
||||
|
||||
var errTest = testutil.TestError()
|
||||
|
||||
type errObject struct{}
|
||||
|
||||
func (e errObject) Encode(b *Buffer) error {
|
||||
return errTest
|
||||
}
|
||||
|
||||
func (e errObject) Decode(b *Buffer) error {
|
||||
return errTest
|
||||
}
|
||||
|
||||
type bytesObject uint32
|
||||
|
||||
func (o bytesObject) Decode(b *Buffer) error {
|
||||
return b.ConsumeID(uint32(o))
|
||||
}
|
||||
|
||||
func (o bytesObject) Encode(b *Buffer) error {
|
||||
b.PutUint32(uint32(o))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBufferEncodeDecode(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{}
|
||||
a.ErrorIs(b.Encode(errObject{}), errTest)
|
||||
a.ErrorIs(b.Decode(errObject{}), errTest)
|
||||
a.NoError(b.Encode(bytesObject(1)))
|
||||
a.NoError(b.Decode(bytesObject(1)))
|
||||
}
|
||||
|
||||
func TestBuffer_Read(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{}
|
||||
|
||||
_, err := b.Read(nil)
|
||||
a.NoError(err)
|
||||
|
||||
_, err = b.Read([]byte{1})
|
||||
a.ErrorIs(err, io.EOF)
|
||||
a.Equal(err, io.EOF)
|
||||
|
||||
b.Put([]byte{1})
|
||||
_, err = b.Read([]byte{1})
|
||||
a.NoError(err)
|
||||
a.Empty(b.Buf)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// encodeBytes is same as encodeString, but for bytes.
|
||||
func encodeBytes(b, v []byte) []byte {
|
||||
l := len(v)
|
||||
if l <= maxSmallStringLength {
|
||||
b = append(b, byte(l))
|
||||
b = append(b, v...)
|
||||
currentLen := l + 1
|
||||
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
|
||||
return b
|
||||
}
|
||||
|
||||
b = append(b, firstLongStringByte, byte(l), byte(l>>8), byte(l>>16))
|
||||
b = append(b, v...)
|
||||
currentLen := l + 4
|
||||
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// decodeBytes is same as decodeString, but for bytes.
|
||||
//
|
||||
// NB: v is slice of b.
|
||||
func decodeBytes(b []byte) (n int, v []byte, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if b[0] == firstLongStringByte {
|
||||
if len(b) < 4 {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
strLen := uint32(b[1]) | uint32(b[2])<<8 | uint32(b[3])<<16
|
||||
if len(b) < (int(strLen) + 4) {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
return nearestPaddedValueLength(int(strLen) + 4), b[4 : strLen+4], nil
|
||||
}
|
||||
strLen := int(b[0])
|
||||
if len(b) < (strLen + 1) {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if strLen > maxSmallStringLength {
|
||||
return 0, nil, &InvalidLengthError{
|
||||
Length: strLen,
|
||||
Where: "bytes",
|
||||
}
|
||||
}
|
||||
return nearestPaddedValueLength(strLen + 1), b[1 : strLen+1], nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBytesDecodeEncode(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
for _, b := range [][]byte{
|
||||
bytes.Repeat([]byte{1, 2, 3, 4}, 100),
|
||||
bytes.Repeat([]byte{1, 2, 3}, 102),
|
||||
bytes.Repeat([]byte{1, 2}, 103),
|
||||
bytes.Repeat([]byte{10}, 104),
|
||||
bytes.Repeat([]byte{6}, 105),
|
||||
[]byte("foo"),
|
||||
[]byte("b"),
|
||||
[]byte("ba"),
|
||||
[]byte("what are you doing?"),
|
||||
[]byte("кек"),
|
||||
{
|
||||
0x57, 0x61, 0x6b, 0x65,
|
||||
0x20, 0x75, 0x70, 0x2c,
|
||||
0x20, 0x4e, 0x65, 0x6f,
|
||||
},
|
||||
bytes.Repeat([]byte{1}, 253),
|
||||
} {
|
||||
buf := encodeBytes(nil, b)
|
||||
a.True(len(buf)%4 == 0, "bad align")
|
||||
|
||||
n, v, err := decodeBytes(buf)
|
||||
a.NoError(err)
|
||||
a.Equal(b, v)
|
||||
a.NotZero(n, "zero bytes read return")
|
||||
}
|
||||
})
|
||||
t.Run("ErrUnexpectedEOF", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
_, _, err := decodeBytes(nil)
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeBytes([]byte{firstLongStringByte})
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeBytes([]byte{firstLongStringByte, 0, 0, 255, 0})
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeBytes(encodeString(nil, "foo bar")[:2])
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeBytes(encodeString(nil, strings.Repeat("b", 105))[:10])
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
})
|
||||
t.Run("InvalidLength", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
_, _, err := decodeBytes(bytes.Repeat([]byte{255}, 256))
|
||||
var e *InvalidLengthError
|
||||
a.ErrorAs(err, &e)
|
||||
a.Equal("bytes", e.Where)
|
||||
a.Contains(e.Error(), "bytes")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package bin
|
||||
|
||||
// PreallocateLimit is a vector pre-allocation limit.
|
||||
const PreallocateLimit = 1024
|
||||
@@ -0,0 +1,210 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// PeekID returns next type id in Buffer, but does not consume it.
|
||||
func (b *Buffer) PeekID() (uint32, error) {
|
||||
if len(b.Buf) < Word {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := binary.LittleEndian.Uint32(b.Buf)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// PeekN returns n bytes from Buffer to target, but does not consume it.
|
||||
//
|
||||
// Returns io.ErrUnexpectedEOF if buffer contains less that n bytes.
|
||||
// Expects that len(target) >= n.
|
||||
func (b *Buffer) PeekN(target []byte, n int) error {
|
||||
if len(b.Buf) < n {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
copy(target, b.Buf[:n])
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID decodes type id from Buffer.
|
||||
func (b *Buffer) ID() (uint32, error) {
|
||||
return b.Uint32()
|
||||
}
|
||||
|
||||
// Uint32 decodes unsigned 32-bit integer from Buffer.
|
||||
func (b *Buffer) Uint32() (uint32, error) {
|
||||
v, err := b.PeekID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b.Buf = b.Buf[Word:]
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint64 decodes 64-bit unsigned integer from Buffer.
|
||||
func (b *Buffer) Uint64() (uint64, error) {
|
||||
const size = Word * 2
|
||||
if len(b.Buf) < size {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := binary.LittleEndian.Uint64(b.Buf)
|
||||
b.Buf = b.Buf[size:]
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Int32 decodes signed 32-bit integer from Buffer.
|
||||
func (b *Buffer) Int32() (int32, error) {
|
||||
v, err := b.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
|
||||
// ConsumeN consumes n bytes from buffer, writing them to target.
|
||||
//
|
||||
// Returns io.ErrUnexpectedEOF if buffer contains less that n bytes.
|
||||
// Expects that len(target) >= n.
|
||||
func (b *Buffer) ConsumeN(target []byte, n int) error {
|
||||
if err := b.PeekN(target, n); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Buf = b.Buf[n:]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bool decodes bare boolean from Buffer.
|
||||
func (b *Buffer) Bool() (bool, error) {
|
||||
v, err := b.PeekID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch v {
|
||||
case TypeTrue:
|
||||
b.Buf = b.Buf[Word:]
|
||||
return true, nil
|
||||
case TypeFalse:
|
||||
b.Buf = b.Buf[Word:]
|
||||
return false, nil
|
||||
default:
|
||||
return false, NewUnexpectedID(v)
|
||||
}
|
||||
}
|
||||
|
||||
// ConsumeID decodes type id from Buffer. If id differs from provided,
|
||||
// the *UnexpectedIDErr{ID: gotID} will be returned and buffer will be
|
||||
// not consumed.
|
||||
func (b *Buffer) ConsumeID(id uint32) error {
|
||||
v, err := b.PeekID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != id {
|
||||
return NewUnexpectedID(v)
|
||||
}
|
||||
b.Buf = b.Buf[Word:]
|
||||
return nil
|
||||
}
|
||||
|
||||
// VectorHeader decodes vector length from Buffer.
|
||||
func (b *Buffer) VectorHeader() (int, error) {
|
||||
if err := b.ConsumeID(TypeVector); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := b.Int()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n < 0 {
|
||||
return 0, &InvalidLengthError{
|
||||
Length: n,
|
||||
Where: "vector",
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// String decodes string from Buffer.
|
||||
func (b *Buffer) String() (string, error) {
|
||||
n, v, err := decodeString(b.Buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(b.Buf) < n {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
b.Buf = b.Buf[n:]
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Bytes decodes byte slice from Buffer.
|
||||
//
|
||||
// NB: returning value is a copy, it's safe to modify it.
|
||||
func (b *Buffer) Bytes() ([]byte, error) {
|
||||
n, v, err := decodeBytes(b.Buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b.Buf) < n {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
b.Buf = b.Buf[n:]
|
||||
return append([]byte(nil), v...), nil
|
||||
}
|
||||
|
||||
// Int decodes integer from Buffer.
|
||||
func (b *Buffer) Int() (int, error) {
|
||||
v, err := b.Int32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
// Double decodes 64-bit floating point from Buffer.
|
||||
func (b *Buffer) Double() (float64, error) {
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Float64frombits(uint64(v)), nil
|
||||
}
|
||||
|
||||
// Int53 decodes 53-bit signed integer from Buffer.
|
||||
func (b *Buffer) Int53() (int64, error) {
|
||||
return b.Long()
|
||||
}
|
||||
|
||||
// Long decodes 64-bit signed integer from Buffer.
|
||||
func (b *Buffer) Long() (int64, error) {
|
||||
v, err := b.Uint64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(v), nil
|
||||
}
|
||||
|
||||
// Int128 decodes 128-bit signed integer from Buffer.
|
||||
func (b *Buffer) Int128() (Int128, error) {
|
||||
v := Int128{}
|
||||
size := len(v)
|
||||
if len(b.Buf) < size {
|
||||
return Int128{}, io.ErrUnexpectedEOF
|
||||
}
|
||||
copy(v[:size], b.Buf[:size])
|
||||
b.Buf = b.Buf[size:]
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Int256 decodes 256-bit signed integer from Buffer.
|
||||
func (b *Buffer) Int256() (Int256, error) {
|
||||
v := Int256{}
|
||||
size := len(v)
|
||||
if len(b.Buf) < size {
|
||||
return Int256{}, io.ErrUnexpectedEOF
|
||||
}
|
||||
copy(v[:size], b.Buf[:size])
|
||||
b.Buf = b.Buf[size:]
|
||||
return v, nil
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuffer_PeekN(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{Buf: []byte{1, 2, 3}}
|
||||
|
||||
target := make([]byte, 4)
|
||||
a.ErrorIs(b.PeekN(target, 4), io.ErrUnexpectedEOF)
|
||||
a.NoError(b.PeekN(target, 3))
|
||||
a.Equal([]byte{1, 2, 3, 0}, target)
|
||||
a.Equal([]byte{1, 2, 3}, b.Buf, "PeekN must not modify buffer")
|
||||
}
|
||||
|
||||
func TestBuffer_ConsumeN(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{Buf: []byte{1, 2, 3}}
|
||||
|
||||
target := make([]byte, 4)
|
||||
a.ErrorIs(b.ConsumeN(target, 4), io.ErrUnexpectedEOF)
|
||||
a.NoError(b.ConsumeN(target, 3))
|
||||
a.Equal([]byte{1, 2, 3, 0}, target)
|
||||
a.Empty(b.Buf, "ConsumeN must modify buffer")
|
||||
}
|
||||
|
||||
func TestDecoder_ID(t *testing.T) {
|
||||
var b Buffer
|
||||
const id = 0x1234
|
||||
b.PutID(id)
|
||||
b.PutString("foo bar")
|
||||
b.PutBool(true)
|
||||
b.PutBytes([]byte{1, 2, 3, 4})
|
||||
b.PutInt32(-150)
|
||||
b.PutInt(-151)
|
||||
b.PutLong(100)
|
||||
b.PutDouble(1.5)
|
||||
b.PutVectorHeader(10)
|
||||
|
||||
a := require.New(t)
|
||||
|
||||
gotID, err := b.ID()
|
||||
a.NoError(err)
|
||||
a.Equal(uint32(id), gotID)
|
||||
gotStr, err := b.String()
|
||||
a.NoError(err)
|
||||
a.Equal("foo bar", gotStr)
|
||||
gotBool, err := b.Bool()
|
||||
a.NoError(err)
|
||||
a.True(gotBool)
|
||||
gotBytes, err := b.Bytes()
|
||||
a.NoError(err)
|
||||
a.Equal([]byte{1, 2, 3, 4}, gotBytes)
|
||||
gotInt32, err := b.Int32()
|
||||
a.NoError(err)
|
||||
a.Equal(int32(-150), gotInt32)
|
||||
gotInt, err := b.Int()
|
||||
a.NoError(err)
|
||||
a.Equal(-151, gotInt)
|
||||
gotLong, err := b.Long()
|
||||
a.NoError(err)
|
||||
a.Equal(int64(100), gotLong)
|
||||
gotDouble, err := b.Double()
|
||||
a.NoError(err)
|
||||
a.Equal(1.5, gotDouble)
|
||||
gotVectorHead, err := b.VectorHeader()
|
||||
a.NoError(err)
|
||||
a.Equal(10, gotVectorHead)
|
||||
require.Zero(t, b.Len(), "buffer should be fully consumed")
|
||||
}
|
||||
|
||||
func TestConsumePeek(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{}
|
||||
b.PutID(0x1)
|
||||
|
||||
var buf [4]byte
|
||||
err := b.PeekN(buf[:], len(buf))
|
||||
a.NoError(err)
|
||||
a.Equal([...]byte{1, 0, 0, 0}, buf)
|
||||
// Check that peek does not increase cursor.
|
||||
a.Equal([]byte{1, 0, 0, 0}, b.Buf)
|
||||
|
||||
id, err := b.PeekID()
|
||||
a.NoError(err)
|
||||
a.Equal(uint32(0x1), id)
|
||||
// Check that peek does not increase cursor.
|
||||
a.Equal([]byte{1, 0, 0, 0}, b.Buf)
|
||||
|
||||
err = b.ConsumeN(buf[:], len(buf))
|
||||
a.NoError(err)
|
||||
// Check that consume increase cursor.
|
||||
a.Len(b.Buf, 0)
|
||||
}
|
||||
|
||||
func TestBuffer_VectorHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
expected int
|
||||
wantErr bool
|
||||
targetError error
|
||||
}{
|
||||
{nil, 0, true, io.ErrUnexpectedEOF},
|
||||
{typeIDToBytes(TypeVector), 0, true, io.ErrUnexpectedEOF},
|
||||
{typeIDToBytes(TypeFalse), 0, true, nil},
|
||||
{append(typeIDToBytes(TypeVector), 0, 0, 0, 0), 0, false, nil},
|
||||
{append(typeIDToBytes(TypeVector), 255, 255, 255, 255), 0, true, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
a := require.New(t)
|
||||
b := &Buffer{Buf: tt.data}
|
||||
|
||||
r, err := b.VectorHeader()
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
if tt.targetError != nil {
|
||||
a.ErrorIs(err, tt.targetError)
|
||||
}
|
||||
a.Zero(r)
|
||||
} else {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_Int(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
expected int
|
||||
wantErr bool
|
||||
targetError error
|
||||
}{
|
||||
{nil, 0, true, io.ErrUnexpectedEOF},
|
||||
{make([]byte, 3), 0, true, io.ErrUnexpectedEOF},
|
||||
{make([]byte, 4), 0, false, nil},
|
||||
{[]byte{0x01, 0x00, 0x00, 0x00}, 1, false, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
a := require.New(t)
|
||||
b := &Buffer{Buf: tt.data}
|
||||
|
||||
r, err := b.Int()
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
if tt.targetError != nil {
|
||||
a.ErrorIs(err, tt.targetError)
|
||||
}
|
||||
a.Zero(r)
|
||||
} else {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_Double(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
expected float64
|
||||
wantErr bool
|
||||
targetError error
|
||||
}{
|
||||
{nil, 0, true, io.ErrUnexpectedEOF},
|
||||
{make([]byte, 7), 0, true, io.ErrUnexpectedEOF},
|
||||
{make([]byte, 8), 0, false, nil},
|
||||
{func() []byte {
|
||||
d := Buffer{}
|
||||
d.PutDouble(1)
|
||||
return d.Buf
|
||||
}(), 1, false, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
a := require.New(t)
|
||||
b := &Buffer{Buf: tt.data}
|
||||
|
||||
r, err := b.Double()
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
if tt.targetError != nil {
|
||||
a.ErrorIs(err, tt.targetError)
|
||||
}
|
||||
a.Zero(r)
|
||||
} else {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_Bool(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
expected bool
|
||||
wantErr bool
|
||||
}{
|
||||
{typeIDToBytes(TypeTrue), true, false},
|
||||
{typeIDToBytes(TypeFalse), false, false},
|
||||
{nil, false, true},
|
||||
{typeIDToBytes(TypeVector), false, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
a := require.New(t)
|
||||
b := &Buffer{Buf: tt.data}
|
||||
|
||||
r, err := b.Bool()
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
a.Zero(r)
|
||||
} else {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_ConsumeID(t *testing.T) {
|
||||
consume := uint32(TypeTrue)
|
||||
tests := []struct {
|
||||
data []byte
|
||||
wantErr bool
|
||||
targetError func(a *require.Assertions, e error)
|
||||
}{
|
||||
{typeIDToBytes(TypeTrue), false, nil},
|
||||
{
|
||||
typeIDToBytes(TypeVector),
|
||||
true,
|
||||
func(a *require.Assertions, e error) {
|
||||
var unexpected *UnexpectedIDErr
|
||||
a.ErrorAs(e, &unexpected)
|
||||
a.Equal(uint32(TypeVector), unexpected.ID)
|
||||
a.NotEmpty(unexpected.Error())
|
||||
},
|
||||
},
|
||||
{nil, true, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
a := require.New(t)
|
||||
b := &Buffer{Buf: tt.data}
|
||||
|
||||
err := b.ConsumeID(consume)
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
if tt.targetError != nil {
|
||||
tt.targetError(a, err)
|
||||
}
|
||||
} else {
|
||||
a.NoError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_String(t *testing.T) {
|
||||
b := Buffer{}
|
||||
_, err := b.String()
|
||||
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
|
||||
b.PutString("ab")
|
||||
b.Buf = b.Buf[:3] // Cut padding
|
||||
|
||||
_, err = b.String()
|
||||
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
}
|
||||
|
||||
func TestBuffer_Bytes(t *testing.T) {
|
||||
b := Buffer{}
|
||||
_, err := b.Bytes()
|
||||
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
|
||||
b.PutBytes([]byte("ab"))
|
||||
b.Buf = b.Buf[:3] // Cut padding
|
||||
|
||||
_, err = b.Bytes()
|
||||
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
)
|
||||
|
||||
// PutID serializes type definition id, like a8509bda.
|
||||
func (b *Buffer) PutID(id uint32) {
|
||||
b.PutUint32(id)
|
||||
}
|
||||
|
||||
// Put appends raw bytes to buffer.
|
||||
//
|
||||
// Buffer does not retain raw.
|
||||
func (b *Buffer) Put(raw []byte) {
|
||||
b.Buf = append(b.Buf, raw...)
|
||||
}
|
||||
|
||||
// PutString serializes bare string.
|
||||
func (b *Buffer) PutString(s string) {
|
||||
b.Buf = encodeString(b.Buf, s)
|
||||
}
|
||||
|
||||
// PutBytes serializes bare byte string.
|
||||
func (b *Buffer) PutBytes(v []byte) {
|
||||
b.Buf = encodeBytes(b.Buf, v)
|
||||
}
|
||||
|
||||
// PutVectorHeader serializes vector header with provided length.
|
||||
func (b *Buffer) PutVectorHeader(length int) {
|
||||
b.PutID(TypeVector)
|
||||
b.PutInt32(int32(length))
|
||||
}
|
||||
|
||||
// PutInt serializes v as signed 32-bit integer.
|
||||
//
|
||||
// If v is bigger than 32-bit, `behavior` is undefined.
|
||||
func (b *Buffer) PutInt(v int) {
|
||||
b.PutInt32(int32(v))
|
||||
}
|
||||
|
||||
// PutBool serializes bare boolean.
|
||||
func (b *Buffer) PutBool(v bool) {
|
||||
switch v {
|
||||
case true:
|
||||
b.PutID(TypeTrue)
|
||||
case false:
|
||||
b.PutID(TypeFalse)
|
||||
}
|
||||
}
|
||||
|
||||
// PutUint16 serializes unsigned 16-bit integer.
|
||||
func (b *Buffer) PutUint16(v uint16) {
|
||||
t := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(t, v)
|
||||
b.Buf = append(b.Buf, t...)
|
||||
}
|
||||
|
||||
// PutInt32 serializes signed 32-bit integer.
|
||||
func (b *Buffer) PutInt32(v int32) {
|
||||
b.PutUint32(uint32(v))
|
||||
}
|
||||
|
||||
// PutUint32 serializes unsigned 32-bit integer.
|
||||
func (b *Buffer) PutUint32(v uint32) {
|
||||
t := make([]byte, Word)
|
||||
binary.LittleEndian.PutUint32(t, v)
|
||||
b.Buf = append(b.Buf, t...)
|
||||
}
|
||||
|
||||
// PutInt53 serializes v as signed integer.
|
||||
func (b *Buffer) PutInt53(v int64) {
|
||||
b.PutLong(v)
|
||||
}
|
||||
|
||||
// PutLong serializes v as signed integer.
|
||||
func (b *Buffer) PutLong(v int64) {
|
||||
b.PutUint64(uint64(v))
|
||||
}
|
||||
|
||||
// PutUint64 serializes v as unsigned 64-bit integer.
|
||||
func (b *Buffer) PutUint64(v uint64) {
|
||||
t := make([]byte, Word*2)
|
||||
binary.LittleEndian.PutUint64(t, v)
|
||||
b.Buf = append(b.Buf, t...)
|
||||
}
|
||||
|
||||
// PutDouble serializes v as 64-bit floating point.
|
||||
func (b *Buffer) PutDouble(v float64) {
|
||||
b.PutUint64(math.Float64bits(v))
|
||||
}
|
||||
|
||||
// PutInt128 serializes v as 128-bit signed integer.
|
||||
func (b *Buffer) PutInt128(v Int128) {
|
||||
b.Buf = append(b.Buf, v[:]...)
|
||||
}
|
||||
|
||||
// PutInt256 serializes v as 256-bit signed integer.
|
||||
func (b *Buffer) PutInt256(v Int256) {
|
||||
b.Buf = append(b.Buf, v[:]...)
|
||||
}
|
||||
|
||||
// Reset buffer to zero length.
|
||||
func (b *Buffer) Reset() {
|
||||
b.Buf = b.Buf[:0]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package bin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/entity"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/styling"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func BenchmarkDecodeSlice(b *testing.B) {
|
||||
builder := entity.Builder{}
|
||||
if err := styling.Perform(&builder,
|
||||
styling.Plain("plaintext"), styling.Plain("\n\n"),
|
||||
styling.Mention("@durov"), styling.Plain("\n\n"),
|
||||
styling.Hashtag("#hashtag"), styling.Plain("\n\n"),
|
||||
styling.BotCommand("/command"), styling.Plain("\n\n"),
|
||||
styling.URL("https://google.org"), styling.Plain("\n\n"),
|
||||
styling.Email("example@example.org"), styling.Plain("\n\n"),
|
||||
styling.Bold("bold"), styling.Plain("\n\n"),
|
||||
styling.Italic("italic"), styling.Plain("\n\n"),
|
||||
styling.Underline("underline"), styling.Plain("\n\n"),
|
||||
styling.Strike("strike"), styling.Plain("\n\n"),
|
||||
styling.Code("fmt.Println(`Hello, World!`)"), styling.Plain("\n\n"),
|
||||
styling.Pre("fmt.Println(`Hello, World!`)", "Go"), styling.Plain("\n\n"),
|
||||
styling.TextURL("clickme", "https://google.com"), styling.Plain("\n\n"),
|
||||
styling.Phone("+71234567891"), styling.Plain("\n\n"),
|
||||
styling.Cashtag("$CASHTAG"), styling.Plain("\n\n"),
|
||||
styling.Blockquote("blockquote", false), styling.Plain("\n\n"),
|
||||
styling.BankCard("5550111111111111"), styling.Plain("\n\n"),
|
||||
); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message, entities := builder.Complete()
|
||||
|
||||
var buf bin.Buffer
|
||||
if err := buf.Encode(&tg.Message{
|
||||
PeerID: &tg.PeerUser{},
|
||||
Message: message,
|
||||
Entities: entities,
|
||||
}); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var msg tg.Message
|
||||
|
||||
if err := msg.Decode(&bin.Buffer{Buf: buf.Buf}); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkBuffer_PutString(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.PutString("Foo bar baz")
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuffer_PutID(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.PutID(TypeStringID)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBufferMultiplePuts(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.PutID(0xbadbad)
|
||||
buf.PutBool(true)
|
||||
buf.PutString("foo")
|
||||
buf.PutLong(12345)
|
||||
buf.PutDouble(10.55)
|
||||
buf.PutInt(10)
|
||||
buf.PutVectorHeader(2)
|
||||
buf.PutInt(1)
|
||||
buf.PutInt(2)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuffer_Put(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
raw := []byte{1, 2, 3}
|
||||
buf.PutID(0xbadbad)
|
||||
buf.Put(raw)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutInt32(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Integer int32
|
||||
Value []byte
|
||||
}{
|
||||
{Integer: 0, Value: []byte{0x00, 0x00, 0x00, 0x00}},
|
||||
{Integer: 1, Value: []byte{0x01, 0x00, 0x00, 0x00}},
|
||||
{Integer: -1, Value: []byte{0xff, 0xff, 0xff, 0xff}},
|
||||
{Integer: math.MaxInt32, Value: []byte{0xff, 0xff, 0xff, 0x7f}},
|
||||
{Integer: math.MinInt32, Value: []byte{0x00, 0x00, 0x00, 0x80}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", tt.Integer), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutInt32(tt.Integer)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
|
||||
t.Run("Int", func(t *testing.T) {
|
||||
b.Reset()
|
||||
b.PutInt(int(tt.Integer))
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutUint32(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Integer uint32
|
||||
Value []byte
|
||||
}{
|
||||
{Integer: 0, Value: []byte{0x00, 0x00, 0x00, 0x00}},
|
||||
{Integer: 1, Value: []byte{0x01, 0x00, 0x00, 0x00}},
|
||||
{Integer: math.MaxUint32, Value: []byte{0xff, 0xff, 0xff, 0xff}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", tt.Integer), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutUint32(tt.Integer)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutLong(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Integer int64
|
||||
Value []byte
|
||||
}{
|
||||
{Integer: 0, Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
|
||||
{Integer: 1, Value: []byte{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
|
||||
{Integer: -1, Value: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{Integer: math.MaxInt64, Value: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
|
||||
{Integer: math.MinInt64, Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", tt.Integer), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutLong(tt.Integer)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutDouble(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Float float64
|
||||
Value []byte
|
||||
}{
|
||||
{Float: 0, Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
|
||||
{Float: 1.5, Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf8, 0x3f}},
|
||||
{Float: -1.5, Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf8, 0xbf}},
|
||||
{Float: math.Inf(1), Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x7f}},
|
||||
{Float: math.Inf(-1), Value: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0xff}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%f", tt.Float), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutDouble(tt.Float)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func typeIDToBytes(id uint32) []byte {
|
||||
b := Buffer{}
|
||||
b.PutID(id)
|
||||
return b.Buf
|
||||
}
|
||||
|
||||
func TestBuffer_PutBool(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Bool bool
|
||||
Value []byte
|
||||
}{
|
||||
{Bool: true, Value: typeIDToBytes(TypeTrue)},
|
||||
{Bool: false, Value: typeIDToBytes(TypeFalse)},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%t", tt.Bool), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutBool(tt.Bool)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutUint16(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Integer uint16
|
||||
Value []byte
|
||||
}{
|
||||
{Integer: 0, Value: []byte{0x00, 0x00}},
|
||||
{Integer: 1, Value: []byte{0x01, 0x00}},
|
||||
{Integer: math.MaxUint16, Value: []byte{0xff, 0xff}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", tt.Integer), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutUint16(tt.Integer)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_PutVectorHeader(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Len int
|
||||
Value []byte
|
||||
}{
|
||||
{
|
||||
Len: 0,
|
||||
Value: []byte{0x15, 0xc4, 0xb5, 0x1c, 0x0, 0x0, 0x0, 0x0},
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("Vec[%d]", tt.Len), func(t *testing.T) {
|
||||
var b Buffer
|
||||
b.PutVectorHeader(tt.Len)
|
||||
require.Equal(t, tt.Value, b.Buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer_Put(t *testing.T) {
|
||||
a := require.New(t)
|
||||
b := Buffer{Buf: []byte{1, 2, 3}}
|
||||
|
||||
b.Put(nil)
|
||||
a.Len(b.Buf, 3)
|
||||
|
||||
b.Put([]byte{})
|
||||
a.Len(b.Buf, 3)
|
||||
|
||||
b.Put([]byte{4})
|
||||
a.Equal([]byte{1, 2, 3, 4}, b.Buf)
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// error code:int32 message:string = Error;
|
||||
type Message struct {
|
||||
Code int32
|
||||
Message string
|
||||
Nonce Int128
|
||||
Key Int256
|
||||
}
|
||||
|
||||
// EncodeTo implements bin.Encoder.
|
||||
func (m Message) Encode(b *Buffer) error {
|
||||
b.PutID(0x9bdd8f1a)
|
||||
b.PutInt32(m.Code)
|
||||
b.PutString(m.Message)
|
||||
b.PutInt128(m.Nonce)
|
||||
b.PutInt256(m.Key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) Decode(b *Buffer) error {
|
||||
if err := b.ConsumeID(0x9bdd8f1a); err != nil {
|
||||
return err
|
||||
}
|
||||
{
|
||||
v, err := b.Int32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Code = v
|
||||
}
|
||||
{
|
||||
v, err := b.String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Message = v
|
||||
}
|
||||
{
|
||||
v, err := b.Int128()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Nonce = v
|
||||
}
|
||||
{
|
||||
v, err := b.Int256()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Key = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEncodeMessage(t *testing.T) {
|
||||
m := Message{
|
||||
Code: 204,
|
||||
Message: "Wake up, Neo",
|
||||
Nonce: [16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF},
|
||||
Key: [32]byte{
|
||||
0xFF, 0xAA, 0xFF, 0xBB, 0xEE, 0x11, 0x12, 0x13, 0x14, 0x10, 0x10, 0x02, 0x04, 0x06, 0x08, 0x0A,
|
||||
0x00, 0x00, 0x00, 0x33, 0x55, 0xEE, 0x16, 0x11, 0x10, 0x14, 0x15, 0x02, 0x10, 0x10, 0x20, 0x20,
|
||||
},
|
||||
}
|
||||
b := new(Buffer)
|
||||
_ = m.Encode(b)
|
||||
expected := []byte{
|
||||
// Type ID.
|
||||
0x1a, 0x8f, 0xdd, 0x9b,
|
||||
|
||||
// Code as int32.
|
||||
204, 0x00, 0x00, 0x00,
|
||||
|
||||
// String length.
|
||||
byte(len(m.Message)),
|
||||
|
||||
// "Wake up, Neo" in hex.
|
||||
0x57, 0x61, 0x6b,
|
||||
0x65, 0x20, 0x75, 0x70,
|
||||
0x2c, 0x20, 0x4e, 0x65,
|
||||
0x6f, 0x00, 0x00, 0x00,
|
||||
|
||||
// Nonce.
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
|
||||
// Key.
|
||||
0xFF, 0xAA, 0xFF, 0xBB, 0xEE, 0x11, 0x12, 0x13,
|
||||
0x14, 0x10, 0x10, 0x02, 0x04, 0x06, 0x08, 0x0A,
|
||||
0x00, 0x00, 0x00, 0x33, 0x55, 0xEE, 0x16, 0x11,
|
||||
0x10, 0x14, 0x15, 0x02, 0x10, 0x10, 0x20, 0x20,
|
||||
}
|
||||
if !bytes.Equal(expected, b.Buf) {
|
||||
t.Log(hex.Dump(b.Buf))
|
||||
}
|
||||
var decoded Message
|
||||
if err := decoded.Decode(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, m, decoded)
|
||||
|
||||
t.Run("BigInt", func(t *testing.T) {
|
||||
// Byte order should not change (as defined in Telegram docs) and should be
|
||||
// "same as openssl".
|
||||
// This rule is correct for cryptographical big integers and parts of SHA1-s.
|
||||
expectedKey, ok := big.NewInt(0).
|
||||
SetString("FFAAFFBBEE111213141010020406080a0000003355EE16111014150210102020", 16)
|
||||
require.True(t, ok)
|
||||
require.Zero(t, expectedKey.Cmp(decoded.Key.BigInt()), "key big.Int unexpected")
|
||||
|
||||
expectedNonce, ok := big.NewInt(0).SetString("000102030405060708090A0B0C0D0E0F", 16)
|
||||
require.True(t, ok)
|
||||
require.Zero(t, expectedNonce.Cmp(decoded.Nonce.BigInt()), "nonce big.Int unexpected")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package bin
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InvalidLengthError is returned when decoder reads invalid length.
|
||||
type InvalidLengthError struct {
|
||||
Length int
|
||||
Where string
|
||||
}
|
||||
|
||||
func (i *InvalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid %s length: %d", i.Where, i.Length)
|
||||
}
|
||||
|
||||
// UnexpectedIDErr means that unknown or unexpected type id was decoded.
|
||||
type UnexpectedIDErr struct {
|
||||
ID uint32
|
||||
}
|
||||
|
||||
func (e *UnexpectedIDErr) Error() string {
|
||||
return fmt.Sprintf("unexpected id %#x", e.ID)
|
||||
}
|
||||
|
||||
// NewUnexpectedID return new UnexpectedIDErr.
|
||||
func NewUnexpectedID(id uint32) error {
|
||||
return &UnexpectedIDErr{ID: id}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package bin
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Fields represent a bitfield value that compactly encodes
|
||||
// information about provided conditional fields, e.g. says
|
||||
// that fields "1", "5" and "10" were set.
|
||||
type Fields uint32
|
||||
|
||||
// Zero returns true, if all bits are equal to zero.
|
||||
func (f Fields) Zero() bool {
|
||||
return f == 0
|
||||
}
|
||||
|
||||
// String implement fmt.Stringer
|
||||
func (f Fields) String() string {
|
||||
return strconv.FormatUint(uint64(f), 2)
|
||||
}
|
||||
|
||||
// Decode implements Decoder.
|
||||
func (f *Fields) Decode(b *Buffer) error {
|
||||
v, err := b.Int32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = Fields(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode implements Encoder.
|
||||
func (f Fields) Encode(b *Buffer) error {
|
||||
b.PutUint32(uint32(f))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Has reports whether field with index n was set.
|
||||
func (f Fields) Has(n int) bool {
|
||||
return f&(1<<n) != 0
|
||||
}
|
||||
|
||||
// Unset unsets field with index n.
|
||||
func (f *Fields) Unset(n int) {
|
||||
*f &= ^(1 << n)
|
||||
}
|
||||
|
||||
// Set sets field with index n.
|
||||
func (f *Fields) Set(n int) {
|
||||
*f |= 1 << n
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
t.Run("Encode", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
var f Fields
|
||||
|
||||
a.True(f.Zero())
|
||||
f.Set(1)
|
||||
f.Set(0)
|
||||
f.Set(10)
|
||||
f.Unset(10)
|
||||
f.Set(5)
|
||||
a.True(f.Has(1))
|
||||
a.True(f.Has(5))
|
||||
a.True(f.Has(0))
|
||||
a.False(f.Has(2))
|
||||
a.False(f.Has(10))
|
||||
a.Equal("100011", f.String())
|
||||
|
||||
var b Buffer
|
||||
a.NoError(f.Encode(&b))
|
||||
var decoded Fields
|
||||
a.NoError(decoded.Decode(&b))
|
||||
a.Equal(f, decoded)
|
||||
})
|
||||
|
||||
t.Run("Decode", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
var decoded Fields
|
||||
a.Error(decoded.Decode(&Buffer{}))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package bin
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Int128 represents signed 128-bit integer.
|
||||
type Int128 [16]byte
|
||||
|
||||
// Decode implements bin.Decoder.
|
||||
func (i *Int128) Decode(buf *Buffer) error {
|
||||
v, err := buf.Int128()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode implements bin.Encoder.
|
||||
func (i Int128) Encode(b *Buffer) error {
|
||||
b.PutInt128(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BigInt returns corresponding big.Int value.
|
||||
func (i Int128) BigInt() *big.Int {
|
||||
return big.NewInt(0).SetBytes(i[:])
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInt128_Encode(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
v := Int128{1, 2, 3, 0, 134, 45}
|
||||
b := Buffer{}
|
||||
a.NoError(v.Encode(&b))
|
||||
var decoded Int128
|
||||
a.NoError(decoded.Decode(&b))
|
||||
a.Equal(v, decoded)
|
||||
a.Error(decoded.Decode(&Buffer{}))
|
||||
}
|
||||
|
||||
func BenchmarkBuffer_PutInt128(b *testing.B) {
|
||||
v := Int128{10, 15}
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.PutInt128(v)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package bin
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Int256 represents signed 256-bit integer.
|
||||
type Int256 [32]byte
|
||||
|
||||
// Decode implements bin.Decoder.
|
||||
func (i *Int256) Decode(buf *Buffer) error {
|
||||
v, err := buf.Int256()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode implements bin.Encoder.
|
||||
func (i Int256) Encode(b *Buffer) error {
|
||||
b.PutInt256(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BigInt returns corresponding big.Int value.
|
||||
func (i Int256) BigInt() *big.Int {
|
||||
return big.NewInt(0).SetBytes(i[:])
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInt256_Encode(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
v := Int256{4, 3, 1, 2}
|
||||
b := Buffer{}
|
||||
a.NoError(v.Encode(&b))
|
||||
var decoded Int256
|
||||
a.NoError(decoded.Decode(&b))
|
||||
a.Equal(v, decoded)
|
||||
a.Error(decoded.Decode(&Buffer{}))
|
||||
}
|
||||
|
||||
func BenchmarkBuffer_PutInt256(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
v := Int256{1, 4, 4, 6}
|
||||
buf := new(Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.PutInt256(v)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package bin
|
||||
|
||||
import "sync"
|
||||
|
||||
// Pool is a bin.Buffer pool.
|
||||
type Pool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// NewPool creates new Pool.
|
||||
// Length is initial buffer length.
|
||||
func NewPool(length int) *Pool {
|
||||
return &Pool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
var r []byte
|
||||
if length > 0 {
|
||||
r = make([]byte, 0, length)
|
||||
}
|
||||
return &Buffer{Buf: r}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Put returns buffer to pool.
|
||||
func (b *Pool) Put(buf *Buffer) {
|
||||
b.pool.Put(buf)
|
||||
}
|
||||
|
||||
// Get takes buffer from pool.
|
||||
func (b *Pool) Get() *Buffer {
|
||||
buf := b.pool.Get().(*Buffer)
|
||||
buf.Reset()
|
||||
return buf
|
||||
}
|
||||
|
||||
// GetSize takes buffer with given size from pool.
|
||||
func (b *Pool) GetSize(length int) *Buffer {
|
||||
buf := b.Get()
|
||||
buf.ResetN(length)
|
||||
|
||||
return buf
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPool_GetSize(t *testing.T) {
|
||||
sizes := []int{0, 1024}
|
||||
|
||||
for _, size := range sizes {
|
||||
a := require.New(t)
|
||||
p := NewPool(size)
|
||||
|
||||
b := p.Get()
|
||||
a.Empty(b.Buf)
|
||||
p.Put(b)
|
||||
|
||||
b = p.GetSize(1024)
|
||||
a.Len(b.Buf, 1024)
|
||||
p.Put(b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package bin
|
||||
|
||||
import "io"
|
||||
|
||||
const (
|
||||
// If L <= 253, the serialization contains one byte with the value of L,
|
||||
// then L bytes of the string followed by 0 to 3 characters containing 0,
|
||||
// such that the overall length of the value be divisible by 4,
|
||||
// whereupon all of this is interpreted as a sequence of int(L/4)+1 32-bit numbers.
|
||||
maxSmallStringLength = 253
|
||||
// If L >= 254, the serialization contains byte 254, followed by 3 bytes with
|
||||
// the string length L, followed by L bytes of the string, further followed
|
||||
// by 0 to 3 null padding bytes.
|
||||
firstLongStringByte = 254
|
||||
)
|
||||
|
||||
func encodeString(b []byte, v string) []byte {
|
||||
l := len(v)
|
||||
if l <= maxSmallStringLength {
|
||||
b = append(b, byte(l))
|
||||
b = append(b, v...)
|
||||
currentLen := l + 1
|
||||
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
|
||||
return b
|
||||
}
|
||||
|
||||
b = append(b, firstLongStringByte, byte(l), byte(l>>8), byte(l>>16))
|
||||
b = append(b, v...)
|
||||
currentLen := l + 4
|
||||
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func decodeString(b []byte) (padding int, v string, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if b[0] == firstLongStringByte {
|
||||
if len(b) < 4 {
|
||||
return 0, "", io.ErrUnexpectedEOF
|
||||
}
|
||||
strLen := uint32(b[1]) | uint32(b[2])<<8 | uint32(b[3])<<16
|
||||
if len(b) < (int(strLen) + 4) {
|
||||
return 0, "", io.ErrUnexpectedEOF
|
||||
}
|
||||
return nearestPaddedValueLength(int(strLen) + 4), string(b[4 : strLen+4]), nil
|
||||
}
|
||||
strLen := int(b[0])
|
||||
if len(b) < (strLen + 1) {
|
||||
return 0, "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if strLen > maxSmallStringLength {
|
||||
return 0, "", &InvalidLengthError{
|
||||
Length: strLen,
|
||||
Where: "string",
|
||||
}
|
||||
}
|
||||
return nearestPaddedValueLength(strLen + 1), string(b[1 : strLen+1]), nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package bin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStringDecodeEncode(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
for _, s := range []string{
|
||||
strings.Repeat("abcd", 100),
|
||||
strings.Repeat("abc", 102),
|
||||
strings.Repeat("de", 103),
|
||||
strings.Repeat("z", 104),
|
||||
strings.Repeat("b", 105),
|
||||
"foo",
|
||||
"b",
|
||||
"ba",
|
||||
"what are you doing?",
|
||||
"кек",
|
||||
strings.Repeat("a", 253),
|
||||
} {
|
||||
buf := encodeString(nil, s)
|
||||
a.True(len(buf)%4 == 0, "bad align")
|
||||
|
||||
n, v, err := decodeString(buf)
|
||||
a.NoError(err)
|
||||
a.Equal(s, v)
|
||||
a.NotZero(n, "zero bytes read return")
|
||||
}
|
||||
})
|
||||
t.Run("ErrUnexpectedEOF", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
_, _, err := decodeString(nil)
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeString([]byte{firstLongStringByte})
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeString([]byte{firstLongStringByte, 0, 0, 255, 0})
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeString(encodeString(nil, "foo bar")[:2])
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
|
||||
_, _, err = decodeString(encodeString(nil, strings.Repeat("b", 105))[:10])
|
||||
a.ErrorIs(err, io.ErrUnexpectedEOF)
|
||||
})
|
||||
t.Run("InvalidLength", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
_, _, err := decodeString(bytes.Repeat([]byte{255}, 256))
|
||||
var e *InvalidLengthError
|
||||
a.ErrorAs(err, &e)
|
||||
a.Equal("string", e.Where)
|
||||
a.Contains(e.Error(), "string")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user