Skip to main content

ferogram_connect/
util.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
13use std::time::Duration;
14
15use crate::error::ConnectError;
16
17/// CRC-32 using the standard IEEE 802.3 polynomial (for Full transport framing).
18pub fn crc32_ieee(data: &[u8]) -> u32 {
19    const POLY: u32 = 0xedb88320;
20    let mut crc: u32 = 0xffffffff;
21    for &byte in data {
22        let mut b = byte as u32;
23        for _ in 0..8 {
24            let mix = (crc ^ b) & 1;
25            crc >>= 1;
26            if mix != 0 {
27                crc ^= POLY;
28            }
29            b >>= 1;
30        }
31    }
32    crc ^ 0xffffffff
33}
34
35/// Minimum body size above which we attempt zlib compression.
36pub const COMPRESSION_THRESHOLD: usize = 512;
37
38const ID_GZIP_PACKED: u32 = 0x3072cfa1;
39const ID_MSGS_ACK: u32 = 0x62d6b459;
40const ID_MSG_CONTAINER: u32 = 0x73f1f8dc;
41
42pub fn random_i64() -> i64 {
43    let mut b = [0u8; 8];
44    getrandom::getrandom(&mut b).expect("getrandom");
45    i64::from_le_bytes(b)
46}
47
48/// Apply ±20 % random jitter to a backoff delay.
49/// Prevents thundering-herd when many clients reconnect simultaneously
50/// (e.g. after a server restart or a shared network outage).
51pub fn jitter_delay(base_ms: u64) -> Duration {
52    // Use two random bytes for the jitter factor (0..=65535 -> 0.80 … 1.20).
53    let mut b = [0u8; 2];
54    getrandom::getrandom(&mut b).unwrap_or(());
55    let rand_frac = u16::from_le_bytes(b) as f64 / 65535.0; // 0.0 … 1.0
56    let factor = 0.80 + rand_frac * 0.40; // 0.80 … 1.20
57    Duration::from_millis((base_ms as f64 * factor) as u64)
58}
59
60pub fn tl_read_bytes(data: &[u8]) -> Option<Vec<u8>> {
61    if data.is_empty() {
62        return Some(vec![]);
63    }
64    let (len, start) = if data[0] < 254 {
65        (data[0] as usize, 1)
66    } else if data.len() >= 4 {
67        (
68            data[1] as usize | (data[2] as usize) << 8 | (data[3] as usize) << 16,
69            4,
70        )
71    } else {
72        return None;
73    };
74    if data.len() < start + len {
75        return None;
76    }
77    Some(data[start..start + len].to_vec())
78}
79
80pub fn tl_read_string(data: &[u8]) -> Option<String> {
81    tl_read_bytes(data).map(|b| String::from_utf8_lossy(&b).into_owned())
82}
83
84pub fn gz_inflate(data: &[u8]) -> Result<Vec<u8>, ConnectError> {
85    use std::io::Read;
86    let mut out = Vec::new();
87    if flate2::read::GzDecoder::new(data)
88        .read_to_end(&mut out)
89        .is_ok()
90        && !out.is_empty()
91    {
92        return Ok(out);
93    }
94    out.clear();
95    flate2::read::ZlibDecoder::new(data)
96        .read_to_end(&mut out)
97        .map_err(|_| ConnectError::other("decompression failed"))?;
98    Ok(out)
99}
100
101pub fn maybe_gz_decompress(body: Vec<u8>) -> Result<Vec<u8>, ConnectError> {
102    const ID_GZIP_PACKED_LOCAL: u32 = 0x3072cfa1;
103    if body.len() >= 4 && u32::from_le_bytes(body[0..4].try_into().unwrap()) == ID_GZIP_PACKED_LOCAL
104    {
105        let bytes = tl_read_bytes(&body[4..]).unwrap_or_default();
106        gz_inflate(&bytes)
107    } else {
108        Ok(body)
109    }
110}
111
112/// TL `bytes` wire encoding (used inside gzip_packed).
113pub fn tl_write_bytes(data: &[u8]) -> Vec<u8> {
114    let len = data.len();
115    let mut out = Vec::with_capacity(4 + len);
116    if len < 254 {
117        out.push(len as u8);
118        out.extend_from_slice(data);
119        let pad = (4 - (1 + len) % 4) % 4;
120        out.extend(std::iter::repeat_n(0u8, pad));
121    } else {
122        out.push(0xfe);
123        out.extend_from_slice(&(len as u32).to_le_bytes()[..3]);
124        out.extend_from_slice(data);
125        let pad = (4 - (4 + len) % 4) % 4;
126        out.extend(std::iter::repeat_n(0u8, pad));
127    }
128    out
129}
130
131/// Wrap `data` in a `gzip_packed#3072cfa1 packed_data:bytes` TL frame.
132pub fn gz_pack_body(data: &[u8]) -> Vec<u8> {
133    use std::io::Write;
134    let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
135    let _ = enc.write_all(data);
136    let compressed = enc.finish().unwrap_or_default();
137    let mut out = Vec::with_capacity(4 + 4 + compressed.len());
138    out.extend_from_slice(&ID_GZIP_PACKED.to_le_bytes());
139    out.extend(tl_write_bytes(&compressed));
140    out
141}
142
143/// Optionally compress `data`.  Returns the compressed `gzip_packed` wrapper
144/// if it is shorter than the original; otherwise returns `data` unchanged.
145pub fn maybe_gz_pack(data: &[u8]) -> Vec<u8> {
146    if data.len() <= COMPRESSION_THRESHOLD {
147        return data.to_vec();
148    }
149    let packed = gz_pack_body(data);
150    if packed.len() < data.len() {
151        packed
152    } else {
153        data.to_vec()
154    }
155}
156
157// +: MsgsAck body builder
158
159/// Build the TL body for `msgs_ack#62d6b459 msg_ids:Vector<long>`.
160pub fn build_msgs_ack_body(msg_ids: &[i64]) -> Vec<u8> {
161    // msgs_ack#62d6b459 msg_ids:Vector<long>
162    // Vector<long>: 0x1cb5c415 + count:int + [i64...]
163    let mut out = Vec::with_capacity(4 + 4 + 4 + msg_ids.len() * 8);
164    out.extend_from_slice(&ID_MSGS_ACK.to_le_bytes());
165    out.extend_from_slice(&0x1cb5c415_u32.to_le_bytes()); // Vector constructor
166    out.extend_from_slice(&(msg_ids.len() as u32).to_le_bytes());
167    for &id in msg_ids {
168        out.extend_from_slice(&id.to_le_bytes());
169    }
170    out
171}
172
173/// Build the body of a `msg_container#73f1f8dc` from a list of
174/// `(msg_id, seqno, body)` inner messages.
175///
176/// The caller is responsible for allocating msg_id and seqno for each entry
177/// via `EncryptedSession::alloc_msg_seqno`.
178pub fn build_container_body(messages: &[(i64, i32, &[u8])]) -> Vec<u8> {
179    let total_body: usize = messages.iter().map(|(_, _, b)| 16 + b.len()).sum();
180    let mut out = Vec::with_capacity(8 + total_body);
181    out.extend_from_slice(&ID_MSG_CONTAINER.to_le_bytes());
182    out.extend_from_slice(&(messages.len() as u32).to_le_bytes());
183    for &(msg_id, seqno, body) in messages {
184        out.extend_from_slice(&msg_id.to_le_bytes());
185        out.extend_from_slice(&seqno.to_le_bytes());
186        out.extend_from_slice(&(body.len() as u32).to_le_bytes());
187        out.extend_from_slice(body);
188    }
189    out
190}