Telegram saat bepergian, bagian 2: protokol biner

Di bagian sebelumnya , kami menjelaskan pendekatan yang digunakan saat menulis parser untuk skema MTProto. Artikel tersebut ternyata sedikit lebih umum dari yang saya harapkan, kali ini saya akan coba ceritakan lebih jauh tentang seluk beluk Telegram.







Klien Go terus berkembang , dan kami akan kembali ke masa lalu dan mengingat bagaimana protokol serializer dan deserializer ditulis untuk itu .







Dasar



Ada dua cara untuk melakukan deserialisasi: dialirkan dan disangga. Dalam praktiknya, di MTProto, pesan yang lebih besar dari satu megabyte tidak dapat dikirim, jadi saya memilih opsi dengan buffer: katakanlah kita selalu dapat menyimpan pesan lengkap di memori.







Anda mendapatkan struktur berikut:







// Buffer implements low level binary (de-)serialization for TL.
type Buffer struct {
    Buf []byte
}
      
      





Namun, MTProto pada dasarnya menyelaraskan nilai dengan 4 byte (32 bit), mari kita masukkan ini ke dalam konstanta:







// Word represents 4-byte sequence.
// Values in TL are generally aligned to Word.
const Word = 4
      
      





Serialisasi



Mengetahui bahwa hampir semua yang ada di MTProto adalah little-endian, kita dapat mulai dengan membuat serialisasi uint32:







// 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...)
}
      
      





Kami akan membuat serialisasi semua nilai lain dengan cara yang sama: pertama, kami mengalokasikan slice (kompiler Go cukup pintar untuk tidak meletakkannya di heap dalam kasus ini, karena ukuran slice kecil dan konstan), lalu kami menulis nilai di sana, lalu menambahkan potongan ke buffer.







, , . , grammers, Rust Telegram.









, , , gotd/td/bin .







uint32:







// Uint32 decodes unsigned 32-bit integer from Buffer.
func (b *Buffer) Uint32() (uint32, error) {
    if len(b.Buf) < Word {
        return 0, io.ErrUnexpectedEOF
    }
    v := binary.LittleEndian.Uint32(b.Buf)
    b.Buf = b.Buf[Word:]
    return v, nil
}
      
      





, , io.ErrUnexpectedEOF



. . .









([]byte



string



) - 4 .







253, , :







b = append(b, byte(l))
b = append(b, v...)
currentLen := l + 1
// Padding:
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
return b
      
      





, 254, little-endian, :







b = append(b, 254, byte(l), byte(l>>8), byte(l>>16))
b = append(b, v...)
currentLen := l + 4
// Padding:
b = append(b, make([]byte, nearestPaddedValueLength(currentLen)-currentLen)...)
      
      





encodeString(b []byte, v string) []byte



b



, :







// PutString serializes bare string.
func (b *Buffer) PutString(s string) {
    b.Buf = encodeString(b.Buf, s)
}
      
      





, . , .









, , . : ID ( #5b38c6c1



, uint32), , .







, ( ):







// msg#9bdd8f1a code:int32 message:string = Message;
type Message struct {
    Code    int32
    Message string
}
      
      





c Buffer



:







// EncodeTo implements bin.Encoder.
func (m Message) Encode(b *Buffer) error {
    b.PutID(0x9bdd8f1a)
    b.PutInt32(m.Code)
    b.PutString(m.Message)
    return nil
}
      
      





Encode, :







m := Message{
    Code:    204,
    Message: "Wake up, Neo",
}
b := new(Buffer)
_ = m.Encode(b)

raw := []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,
}
      
      





, . Buf, :







// 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
}
      
      





ConsumeID(id uint32)



: PeekID



, . :







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
    }

    return nil
}
      
      





(-) , :







// 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
}
      
      







, .









. :







messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
      
      





, title, users?







Vector :







vector#0x1cb5c415 {t:Type} # [ t ] = Vector t
      
      





. , , .







: (0x1cb5c415), , :







// PutVectorHeader serializes vector header with provided length.
func (b *Buffer) PutVectorHeader(length int) {
    b.PutID(TypeVector)
    b.PutInt32(int32(length))
}
      
      





, 10 uint32, PutVectorHeader(10)



, 10 uint32.









, , :







boolTrue#997275b5 = Bool;
boolFalse#bc799737 = Bool;
      
      





, Bool, 0x997275b5, 0xbc799737:







const (
    TypeTrue  = 0x997275b5 // boolTrue#997275b5 = Bool
    TypeFalse = 0xbc799737 // boolFalse#bc799737 = Bool    
)

// PutBool serializes bare boolean.
func (b *Buffer) PutBool(v bool) {
    switch v {
    case true:
        b.PutID(TypeTrue)
    case false:
        b.PutID(TypeFalse)
    }
}
      
      





, , , .









, , . : , (-), , .







(flags.0?true



): , 0x997275b5



, .







! flags.0?Bool



, Bool, , . , legacy.







bitfield Go :







// Fields represent a bitfield value that compactly encodes
// information about provided conditional fields.
type Fields uint32

// Has reports whether field with index n was set.
func (f Fields) Has(n int) bool {
    return f&(1<<n) != 0
}

// Set sets field with index n.
func (f *Fields) Set(n int) {
    *f |= 1 << n
}
      
      





uint32.







:







// msg flags:# escape:flags.0?true ttl_seconds:flags.1?int = Message;
type FieldsMessage struct {
    Flags      bin.Fields
    Escape     bool
    TTLSeconds int
}

func (f *FieldsMessage) Encode(b *bin.Buffer) error {
    b.PutID(FieldsMessageTypeID)
    if f.Escape {
        f.Flags.Set(0)
    }
    if err := f.Flags.Encode(b); err != nil {
        return err
    }
    if f.Flags.Has(1) {
        b.PutInt(f.TTLSeconds)
    }
    return nil
}
      
      





, TTLSeconds



1



, Escape



Flags



.









int128 int256:







int128 4*[ int ] = Int128;
int256 8*[ int ] = Int256;
      
      





go :







type Int128 [16]byte
type Int256 [32]byte
      
      





, :







func (b *Buffer) PutInt128(v Int128) {
    b.Buf = append(b.Buf, v[:]...)
}
func (b *Buffer) PutInt256(v Int256) {
    b.Buf = append(b.Buf, v[:]...)
}
      
      





, big.Int.







MTProto big-endian OpenSSL. Go big.Int



.







, SetBytes FillBytes :







var v Int256

i := new(big.Int).SetBytes(v[:]) // v -> i
i.FillBytes(v[:]) // i -> v
      
      







bin



, . , , .







Masalah ini diselesaikan dengan menghasilkan (de-) kode serialisasi dari skema (itulah mengapa kami menulis parser!). Mungkin saya akan memberikan generator bagian terpisah dalam serangkaian artikel. Modul proyek ini ternyata rumit, telah ditulis ulang beberapa kali dan saya ingin membuat hidup sedikit lebih mudah bagi orang-orang yang akan menulis generator kode di Go untuk format lain.







Sebagai referensi, sekitar 180K SLOC saat ini dihasilkan dari skema telegram (api, mtproto, obrolan rahasia).







Saya ingin berterima kasih kepada tdakkota dan zweihander atas kontribusinya yang tak ternilai bagi pengembangan proyek! Akan sangat sulit tanpamu.








All Articles