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:
Adam Van Ymeren
2025-06-27 20:03:37 -07:00
committed by GitHub
parent 0952df0244
commit 7a04f298d2
19264 changed files with 1539697 additions and 84 deletions
+62
View File
@@ -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
}
+71
View File
@@ -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
}
+110
View File
@@ -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)
}
+54
View File
@@ -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
}
+70
View File
@@ -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")
})
}
+4
View File
@@ -0,0 +1,4 @@
package bin
// PreallocateLimit is a vector pre-allocation limit.
const PreallocateLimit = 1024
+210
View File
@@ -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
}
+280
View File
@@ -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)
}
+107
View File
@@ -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]
}
+56
View File
@@ -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)
}
}
}
+206
View File
@@ -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)
}
+125
View File
@@ -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")
})
}
+27
View File
@@ -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}
}
+49
View File
@@ -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
}
+40
View File
@@ -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{}))
})
}
+27
View File
@@ -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[:])
}
+28
View File
@@ -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()
}
}
+27
View File
@@ -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[:])
}
+29
View File
@@ -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()
}
}
+44
View File
@@ -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
}
+24
View File
@@ -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)
}
}
+60
View File
@@ -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
}
+65
View File
@@ -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")
})
}