Skip to main content

layer_client/
transport_intermediate.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! MTProto Intermediate and Full transport framing.
20//!
21//! Alongside the existing Abridged transport this module provides:
22//!
23//! * [`IntermediateTransport`]: each packet is `[4-byte LE length][payload]`.
24//!   More compatible than Abridged with proxies that inspect the first byte.
25//!
26//! * [`FullTransport`]: like Intermediate but additionally includes a running
27//!   sequence number and a CRC-32 checksum for integrity verification.
28
29use crate::InvocationError;
30use tokio::io::{AsyncReadExt, AsyncWriteExt};
31use tokio::net::TcpStream;
32
33// Intermediate
34
35/// [MTProto Intermediate] transport framing.
36///
37/// Init byte: `0xeeeeeeee` (4 bytes).  Each message is prefixed with its
38/// 4-byte little-endian byte length.
39///
40/// [MTProto Intermediate]: https://core.telegram.org/mtproto/mtproto-transports#intermediate
41pub struct IntermediateTransport {
42    stream: TcpStream,
43    init_sent: bool,
44}
45
46impl IntermediateTransport {
47    /// Connect and send the 4-byte init header.
48    pub async fn connect(addr: &str) -> Result<Self, InvocationError> {
49        let stream = TcpStream::connect(addr).await?;
50        Ok(Self {
51            stream,
52            init_sent: false,
53        })
54    }
55
56    /// Wrap an existing stream (the init byte will be sent on first [`send`]).
57    pub fn from_stream(stream: TcpStream) -> Self {
58        Self {
59            stream,
60            init_sent: false,
61        }
62    }
63
64    /// Send a message with Intermediate framing.
65    pub async fn send(&mut self, data: &[u8]) -> Result<(), InvocationError> {
66        if !self.init_sent {
67            self.stream.write_all(&[0xee, 0xee, 0xee, 0xee]).await?;
68            self.init_sent = true;
69        }
70        let len = (data.len() as u32).to_le_bytes();
71        self.stream.write_all(&len).await?;
72        self.stream.write_all(data).await?;
73        Ok(())
74    }
75
76    /// Receive the next Intermediate-framed message.
77    pub async fn recv(&mut self) -> Result<Vec<u8>, InvocationError> {
78        let mut len_buf = [0u8; 4];
79        self.stream.read_exact(&mut len_buf).await?;
80        let len = u32::from_le_bytes(len_buf) as usize;
81        let mut buf = vec![0u8; len];
82        self.stream.read_exact(&mut buf).await?;
83        Ok(buf)
84    }
85
86    pub fn into_inner(self) -> TcpStream {
87        self.stream
88    }
89}
90
91// Full
92
93/// [MTProto Full] transport framing.
94///
95/// Extends Intermediate with:
96/// * 4-byte little-endian **sequence number** (auto-incremented per message).
97/// * 4-byte **CRC-32** at the end of each packet covering
98///   `[len][seq_no][payload]`.
99///
100/// No init byte is sent; the full format is detected by the absence of
101/// `0xef` / `0xee` in the first byte.
102///
103/// [MTProto Full]: https://core.telegram.org/mtproto/mtproto-transports#full
104pub struct FullTransport {
105    stream: TcpStream,
106    send_seqno: u32,
107    recv_seqno: u32,
108}
109
110impl FullTransport {
111    pub async fn connect(addr: &str) -> Result<Self, InvocationError> {
112        let stream = TcpStream::connect(addr).await?;
113        Ok(Self {
114            stream,
115            send_seqno: 0,
116            recv_seqno: 0,
117        })
118    }
119
120    pub fn from_stream(stream: TcpStream) -> Self {
121        Self {
122            stream,
123            send_seqno: 0,
124            recv_seqno: 0,
125        }
126    }
127
128    /// Send a message with Full framing (length + seqno + payload + crc32).
129    pub async fn send(&mut self, data: &[u8]) -> Result<(), InvocationError> {
130        let total_len = (data.len() + 12) as u32; // len field + seqno + payload + crc
131        let seq = self.send_seqno;
132        self.send_seqno = self.send_seqno.wrapping_add(1);
133
134        let mut packet = Vec::with_capacity(total_len as usize);
135        packet.extend_from_slice(&total_len.to_le_bytes());
136        packet.extend_from_slice(&seq.to_le_bytes());
137        packet.extend_from_slice(data);
138
139        let crc = crc32_ieee(&packet);
140        packet.extend_from_slice(&crc.to_le_bytes());
141
142        self.stream.write_all(&packet).await?;
143        Ok(())
144    }
145
146    /// Receive the next Full-framed message; validates the CRC-32.
147    pub async fn recv(&mut self) -> Result<Vec<u8>, InvocationError> {
148        let mut len_buf = [0u8; 4];
149        self.stream.read_exact(&mut len_buf).await?;
150        let total_len = u32::from_le_bytes(len_buf) as usize;
151        if total_len < 12 {
152            return Err(InvocationError::Deserialize(
153                "Full transport: packet too short".into(),
154            ));
155        }
156        let mut rest = vec![0u8; total_len - 4];
157        self.stream.read_exact(&mut rest).await?;
158
159        // Verify CRC
160        let (body, crc_bytes) = rest.split_at(rest.len() - 4);
161        let expected_crc = u32::from_le_bytes(crc_bytes.try_into().unwrap());
162        let mut check_input = len_buf.to_vec();
163        check_input.extend_from_slice(body);
164        let actual_crc = crc32_ieee(&check_input);
165        if actual_crc != expected_crc {
166            return Err(InvocationError::Deserialize(format!(
167                "Full transport: CRC mismatch (got {actual_crc:#010x}, expected {expected_crc:#010x})"
168            )));
169        }
170
171        // seq_no is the first 4 bytes of `body`
172        let _recv_seq = u32::from_le_bytes(body[..4].try_into().unwrap());
173        self.recv_seqno = self.recv_seqno.wrapping_add(1);
174
175        Ok(body[4..].to_vec())
176    }
177
178    pub fn into_inner(self) -> TcpStream {
179        self.stream
180    }
181}
182
183// CRC-32 (IEEE 802.3 polynomial)
184
185/// Compute CRC-32 using the standard IEEE 802.3 polynomial.
186pub(crate) fn crc32_ieee(data: &[u8]) -> u32 {
187    const POLY: u32 = 0xedb88320;
188    let mut crc: u32 = 0xffffffff;
189    for &byte in data {
190        let mut b = byte as u32;
191        for _ in 0..8 {
192            let mix = (crc ^ b) & 1;
193            crc >>= 1;
194            if mix != 0 {
195                crc ^= POLY;
196            }
197            b >>= 1;
198        }
199    }
200    crc ^ 0xffffffff
201}