Skip to main content

layer_client/
transport_intermediate.rs

1//! MTProto Intermediate and Full transport framing.
2//!
3//! Alongside the existing Abridged transport this module provides:
4//!
5//! * [`IntermediateTransport`] — each packet is `[4-byte LE length][payload]`.
6//!   More compatible than Abridged with proxies that inspect the first byte.
7//!
8//! * [`FullTransport`] — like Intermediate but additionally includes a running
9//!   sequence number and a CRC-32 checksum for integrity verification.
10
11
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13use tokio::net::TcpStream;
14use crate::InvocationError;
15
16// ─── Intermediate ─────────────────────────────────────────────────────────────
17
18/// [MTProto Intermediate] transport framing.
19///
20/// Init byte: `0xeeeeeeee` (4 bytes).  Each message is prefixed with its
21/// 4-byte little-endian byte length.
22///
23/// [MTProto Intermediate]: https://core.telegram.org/mtproto/mtproto-transports#intermediate
24pub struct IntermediateTransport {
25    stream: TcpStream,
26    init_sent: bool,
27}
28
29impl IntermediateTransport {
30    /// Connect and send the 4-byte init header.
31    pub async fn connect(addr: &str) -> Result<Self, InvocationError> {
32        let stream = TcpStream::connect(addr).await?;
33        Ok(Self { stream, init_sent: false })
34    }
35
36    /// Wrap an existing stream (the init byte will be sent on first [`send`]).
37    pub fn from_stream(stream: TcpStream) -> Self {
38        Self { stream, init_sent: false }
39    }
40
41    /// Send a message with Intermediate framing.
42    pub async fn send(&mut self, data: &[u8]) -> Result<(), InvocationError> {
43        if !self.init_sent {
44            self.stream.write_all(&[0xee, 0xee, 0xee, 0xee]).await?;
45            self.init_sent = true;
46        }
47        let len = (data.len() as u32).to_le_bytes();
48        self.stream.write_all(&len).await?;
49        self.stream.write_all(data).await?;
50        Ok(())
51    }
52
53    /// Receive the next Intermediate-framed message.
54    pub async fn recv(&mut self) -> Result<Vec<u8>, InvocationError> {
55        let mut len_buf = [0u8; 4];
56        self.stream.read_exact(&mut len_buf).await?;
57        let len = u32::from_le_bytes(len_buf) as usize;
58        let mut buf = vec![0u8; len];
59        self.stream.read_exact(&mut buf).await?;
60        Ok(buf)
61    }
62
63    pub fn into_inner(self) -> TcpStream { self.stream }
64}
65
66// ─── Full ─────────────────────────────────────────────────────────────────────
67
68/// [MTProto Full] transport framing.
69///
70/// Extends Intermediate with:
71/// * 4-byte little-endian **sequence number** (auto-incremented per message).
72/// * 4-byte **CRC-32** at the end of each packet covering
73///   `[len][seq_no][payload]`.
74///
75/// No init byte is sent; the full format is detected by the absence of
76/// `0xef` / `0xee` in the first byte.
77///
78/// [MTProto Full]: https://core.telegram.org/mtproto/mtproto-transports#full
79pub struct FullTransport {
80    stream: TcpStream,
81    send_seqno: u32,
82    recv_seqno: u32,
83}
84
85impl FullTransport {
86    pub async fn connect(addr: &str) -> Result<Self, InvocationError> {
87        let stream = TcpStream::connect(addr).await?;
88        Ok(Self { stream, send_seqno: 0, recv_seqno: 0 })
89    }
90
91    pub fn from_stream(stream: TcpStream) -> Self {
92        Self { stream, send_seqno: 0, recv_seqno: 0 }
93    }
94
95    /// Send a message with Full framing (length + seqno + payload + crc32).
96    pub async fn send(&mut self, data: &[u8]) -> Result<(), InvocationError> {
97        let total_len = (data.len() + 12) as u32; // len field + seqno + payload + crc
98        let seq       = self.send_seqno;
99        self.send_seqno = self.send_seqno.wrapping_add(1);
100
101        let mut packet = Vec::with_capacity(total_len as usize);
102        packet.extend_from_slice(&total_len.to_le_bytes());
103        packet.extend_from_slice(&seq.to_le_bytes());
104        packet.extend_from_slice(data);
105
106        let crc = crc32_ieee(&packet);
107        packet.extend_from_slice(&crc.to_le_bytes());
108
109        self.stream.write_all(&packet).await?;
110        Ok(())
111    }
112
113    /// Receive the next Full-framed message; validates the CRC-32.
114    pub async fn recv(&mut self) -> Result<Vec<u8>, InvocationError> {
115        let mut len_buf = [0u8; 4];
116        self.stream.read_exact(&mut len_buf).await?;
117        let total_len = u32::from_le_bytes(len_buf) as usize;
118        if total_len < 12 {
119            return Err(InvocationError::Deserialize("Full transport: packet too short".into()));
120        }
121        let mut rest = vec![0u8; total_len - 4];
122        self.stream.read_exact(&mut rest).await?;
123
124        // Verify CRC
125        let (body, crc_bytes) = rest.split_at(rest.len() - 4);
126        let expected_crc = u32::from_le_bytes(crc_bytes.try_into().unwrap());
127        let mut check_input = len_buf.to_vec();
128        check_input.extend_from_slice(body);
129        let actual_crc = crc32_ieee(&check_input);
130        if actual_crc != expected_crc {
131            return Err(InvocationError::Deserialize(format!(
132                "Full transport: CRC mismatch (got {actual_crc:#010x}, expected {expected_crc:#010x})"
133            )));
134        }
135
136        // seq_no is the first 4 bytes of `body`
137        let _recv_seq = u32::from_le_bytes(body[..4].try_into().unwrap());
138        self.recv_seqno = self.recv_seqno.wrapping_add(1);
139
140        Ok(body[4..].to_vec())
141    }
142
143    pub fn into_inner(self) -> TcpStream { self.stream }
144}
145
146// ─── CRC-32 (IEEE 802.3 polynomial) ──────────────────────────────────────────
147
148/// Compute CRC-32 using the standard IEEE 802.3 polynomial.
149fn crc32_ieee(data: &[u8]) -> u32 {
150    const POLY: u32 = 0xedb88320;
151    let mut crc: u32 = 0xffffffff;
152    for &byte in data {
153        let mut b = byte as u32;
154        for _ in 0..8 {
155            let mix = (crc ^ b) & 1;
156            crc >>= 1;
157            if mix != 0 { crc ^= POLY; }
158            b >>= 1;
159        }
160    }
161    crc ^ 0xffffffff
162}