You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
2.9 KiB

package codec
import (
"hash/crc32"
"io"
"sync/atomic"
"github.com/go-faster/errors"
"github.com/gotd/td/bin"
)
// Full is full MTProto transport.
//
// See https://core.telegram.org/mtproto/mtproto-transports#full
type Full struct {
wSeqNo int64
rSeqNo int64
}
// WriteHeader sends protocol tag.
func (i *Full) WriteHeader(w io.Writer) (err error) {
return nil
}
// ReadHeader reads protocol tag.
func (i *Full) ReadHeader(r io.Reader) (err error) {
return nil
}
// Write encode to writer message from given buffer.
func (i *Full) Write(w io.Writer, b *bin.Buffer) error {
if err := checkOutgoingMessage(b); err != nil {
return err
}
if err := writeFull(w, int(atomic.AddInt64(&i.wSeqNo, 1)-1), b); err != nil {
return errors.Wrap(err, "write full")
}
return nil
}
// Read fills buffer with received message.
func (i *Full) Read(r io.Reader, b *bin.Buffer) error {
if err := readFull(r, int(atomic.AddInt64(&i.rSeqNo, 1)-1), b); err != nil {
return errors.Wrap(err, "read full")
}
return checkProtocolError(b)
}
func writeFull(w io.Writer, seqNo int, b *bin.Buffer) error {
write := bin.Buffer{Buf: make([]byte, 0, 4+4+b.Len()+4)}
// Length: length+seqno+payload+crc length encoded as 4 length bytes
// (little endian, the length of the length field must be included, too)
write.PutInt(4 + 4 + b.Len() + 4)
// Seqno: the TCP sequence number for this TCP connection (different from the MTProto sequence number):
// the first packet sent is numbered 0, the next one 1, etc.
write.PutInt(seqNo)
// payload: MTProto payload
write.Put(b.Raw())
// crc: 4 CRC32 bytes computed using length, sequence number, and payload together.
crc := crc32.ChecksumIEEE(write.Raw())
write.PutUint32(crc)
if _, err := w.Write(write.Raw()); err != nil {
return err
}
return nil
}
var errSeqNoMismatch = errors.New("seq_no mismatch")
var errCRCMismatch = errors.New("crc mismatch")
func readFull(r io.Reader, seqNo int, b *bin.Buffer) error {
n, err := readLen(r, b)
if err != nil {
return errors.Wrap(err, "len")
}
// Put length, because it need to count CRC.
b.PutInt(n)
b.Expand(n - bin.Word)
inner := &bin.Buffer{Buf: b.Buf[bin.Word:n]}
// Reads tail of packet to the buffer.
// Length already read.
if _, err := io.ReadFull(r, inner.Buf); err != nil {
return errors.Wrap(err, "read seqno, buffer and crc")
}
serverSeqNo, err := inner.Int()
if err != nil {
return err
}
if serverSeqNo != seqNo {
return errSeqNoMismatch
}
payloadLength := n - 3*bin.Word
inner.Skip(payloadLength)
// Cut only crc part.
crc, err := inner.Uint32()
if err != nil {
return err
}
// Compute crc using all buffer without last 4 bytes from server.
clientCRC := crc32.ChecksumIEEE(b.Buf[0 : n-bin.Word])
// Compare computed and read CRCs.
if crc != clientCRC {
return errCRCMismatch
}
// n
// Length | SeqNo | payload | CRC |
// Word | Word | ....... | Word |
copy(b.Buf, b.Buf[2*bin.Word:n-bin.Word])
b.Buf = b.Buf[:payloadLength]
return nil
}