Skip to main content

ferogram_connect/
transport_obfuscated.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2//
3// ferogram: async Telegram MTProto client in Rust
4// https://github.com/ankit-chaubey/ferogram
5//
6// Licensed under either the MIT License or the Apache License 2.0.
7// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
8// https://github.com/ankit-chaubey/ferogram
9//
10// Feel free to use, modify, and share this code.
11// Please keep this notice when redistributing.
12
13pub use ferogram_crypto::ObfuscatedCipher;
14
15use crate::ConnectError;
16use tokio::io::{AsyncReadExt, AsyncWriteExt};
17use tokio::net::TcpStream;
18
19/// Framing mode for `ObfuscatedStream`.
20///
21/// * `Abridged` - Obfuscated2 over Abridged framing (`0xEFEFEFEF` nonce tag).
22///   Used for plain and `0x??` MTProxy secrets.
23/// * `PaddedIntermediate` - Obfuscated2 over Padded Intermediate framing
24///   (`0xDDDDDDDD` nonce tag).  Required for `0xDD` MTProxy secrets.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum ObfuscatedFraming {
27    Abridged,
28    PaddedIntermediate,
29}
30
31pub struct ObfuscatedStream {
32    stream: TcpStream,
33    cipher: ObfuscatedCipher,
34    framing: ObfuscatedFraming,
35}
36
37impl ObfuscatedStream {
38    /// Connect using Abridged framing (plain MTProxy secret, no 0xDD prefix).
39    pub async fn connect(
40        addr: &str,
41        proxy_secret: Option<&[u8; 16]>,
42        dc_id: i16,
43    ) -> Result<Self, ConnectError> {
44        let stream = TcpStream::connect(addr).await?;
45        Self::handshake(stream, proxy_secret, dc_id, ObfuscatedFraming::Abridged).await
46    }
47
48    /// Connect using Padded Intermediate framing (0xDD MTProxy secret).
49    pub async fn connect_padded(
50        addr: &str,
51        proxy_secret: Option<&[u8; 16]>,
52        dc_id: i16,
53    ) -> Result<Self, ConnectError> {
54        let stream = TcpStream::connect(addr).await?;
55        Self::handshake(
56            stream,
57            proxy_secret,
58            dc_id,
59            ObfuscatedFraming::PaddedIntermediate,
60        )
61        .await
62    }
63
64    async fn handshake(
65        mut stream: TcpStream,
66        proxy_secret: Option<&[u8; 16]>,
67        dc_id: i16,
68        framing: ObfuscatedFraming,
69    ) -> Result<Self, ConnectError> {
70        let framing_byte = match framing {
71            ObfuscatedFraming::Abridged => 0xef,
72            ObfuscatedFraming::PaddedIntermediate => 0xdd,
73        };
74        let secret = proxy_secret.map(|s| s.as_ref());
75        let (nonce, cipher) = ferogram_crypto::build_obfuscated_init(framing_byte, dc_id, secret);
76        stream.write_all(&nonce).await?;
77        Ok(Self {
78            stream,
79            cipher,
80            framing,
81        })
82    }
83
84    pub async fn send(&mut self, data: &[u8]) -> Result<(), ConnectError> {
85        match self.framing {
86            ObfuscatedFraming::Abridged => {
87                debug_assert_eq!(
88                    data.len() % 4,
89                    0,
90                    "obfuscated send: payload must be 4-byte aligned"
91                );
92                let words = data.len() / 4;
93                let mut frame = if words < 0x7f {
94                    let mut v = Vec::with_capacity(1 + data.len());
95                    v.push(words as u8);
96                    v
97                } else {
98                    let mut v = Vec::with_capacity(4 + data.len());
99                    v.extend_from_slice(&[
100                        0x7f,
101                        (words & 0xff) as u8,
102                        ((words >> 8) & 0xff) as u8,
103                        ((words >> 16) & 0xff) as u8,
104                    ]);
105                    v
106                };
107                frame.extend_from_slice(data);
108                self.cipher.encrypt(&mut frame);
109                self.stream.write_all(&frame).await?;
110            }
111            ObfuscatedFraming::PaddedIntermediate => {
112                // Padded intermediate framing: 4-byte LE length of
113                // (payload + random 0-15 padding), then payload, then padding.
114                let mut pad_len_buf = [0u8; 1];
115                ferogram_crypto::fill_random(&mut pad_len_buf);
116                let pad_len = (pad_len_buf[0] & 0x0f) as usize;
117                let total_payload = data.len() + pad_len;
118                let mut frame = Vec::with_capacity(4 + total_payload);
119                frame.extend_from_slice(&(total_payload as u32).to_le_bytes());
120                frame.extend_from_slice(data);
121                let mut pad = vec![0u8; pad_len];
122                ferogram_crypto::fill_random(&mut pad);
123                frame.extend_from_slice(&pad);
124                self.cipher.encrypt(&mut frame);
125                self.stream.write_all(&frame).await?;
126            }
127        }
128        Ok(())
129    }
130
131    pub async fn recv(&mut self) -> Result<Vec<u8>, ConnectError> {
132        match self.framing {
133            ObfuscatedFraming::Abridged => {
134                let mut h = [0u8; 1];
135                self.stream.read_exact(&mut h).await?;
136                self.cipher.decrypt(&mut h);
137
138                let words = if h[0] < 0x7f {
139                    h[0] as usize
140                } else if h[0] == 0x7f {
141                    let mut b = [0u8; 3];
142                    self.stream.read_exact(&mut b).await?;
143                    self.cipher.decrypt(&mut b);
144                    b[0] as usize | (b[1] as usize) << 8 | (b[2] as usize) << 16
145                } else {
146                    let mut rest = [0u8; 3];
147                    self.stream.read_exact(&mut rest).await?;
148                    self.cipher.decrypt(&mut rest);
149                    let code = i32::from_le_bytes([h[0], rest[0], rest[1], rest[2]]);
150                    return Err(ConnectError::Io(std::io::Error::new(
151                        std::io::ErrorKind::ConnectionRefused,
152                        format!("transport error from server: {code}"),
153                    )));
154                };
155
156                let mut buf = vec![0u8; words * 4];
157                self.stream.read_exact(&mut buf).await?;
158                self.cipher.decrypt(&mut buf);
159
160                if buf.len() == 4 {
161                    let code = i32::from_le_bytes(buf[..4].try_into().unwrap());
162                    if code < 0 {
163                        return Err(ConnectError::Io(std::io::Error::new(
164                            std::io::ErrorKind::ConnectionRefused,
165                            format!("transport error from server: {code}"),
166                        )));
167                    }
168                }
169
170                Ok(buf)
171            }
172            ObfuscatedFraming::PaddedIntermediate => {
173                let mut len_buf = [0u8; 4];
174                self.stream.read_exact(&mut len_buf).await?;
175                self.cipher.decrypt(&mut len_buf);
176                let total_len = i32::from_le_bytes(len_buf);
177                if total_len < 0 {
178                    return Err(ConnectError::Io(std::io::Error::new(
179                        std::io::ErrorKind::ConnectionRefused,
180                        format!("transport error from server: {total_len}"),
181                    )));
182                }
183                let mut buf = vec![0u8; total_len as usize];
184                self.stream.read_exact(&mut buf).await?;
185                self.cipher.decrypt(&mut buf);
186                if buf.len() >= 24 {
187                    let pad = (buf.len() - 24) % 16;
188                    buf.truncate(buf.len() - pad);
189                }
190                Ok(buf)
191            }
192        }
193    }
194}