Skip to main content

ferogram_mtproto/
message.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::{SystemTime, UNIX_EPOCH};
14
15/// A 64-bit MTProto message identifier.
16///
17/// Per the spec: the lower 32 bits are derived from the current Unix time;
18/// the upper 32 bits are a monotonically increasing counter within the second.
19/// The least significant two bits must be zero for client messages.
20#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub struct MessageId(pub u64);
22
23impl MessageId {
24    /// Generate a new message ID using the system clock and the session-local counter.
25    ///
26    /// MTProto msg_id layout:
27    ///   bits 63–32: Unix timestamp in seconds (upper 32 bits)
28    ///   bits 31–2:  intra-second sequencing counter (lower 30 bits, × 4)
29    ///   bits 1–0:   must be 0b00 for client messages
30    ///
31    /// The previous implementation accepted a `_counter` parameter but silently
32    /// ignored it, routing all calls through a process-wide `GLOBAL_MSG_COUNTER`.
33    /// The session-local `msg_counter` in `Session` was incremented uselessly.
34    /// Uses the caller-supplied `counter` directly so each `Session` instance
35    /// drives its own monotonic sequence without a global side-channel.
36    pub(crate) fn generate(counter: u32) -> Self {
37        let unix_secs = SystemTime::now()
38            .duration_since(UNIX_EPOCH)
39            .unwrap_or_default()
40            .as_secs();
41        // upper 32 bits = seconds, lower 30 bits = counter × 4 (bits 1-0 = 0b00)
42        let id = (unix_secs << 32) | (u64::from(counter) << 2);
43        Self(id)
44    }
45}
46
47/// A framed MTProto message ready to be sent.
48#[derive(Debug)]
49pub struct Message {
50    /// Unique identifier for this message.
51    pub id: MessageId,
52    /// Session-scoped sequence number (even for content-unrelated, odd for content-related).
53    pub seq_no: i32,
54    /// The serialized TL body (constructor ID + fields).
55    pub body: Vec<u8>,
56}
57
58impl Message {
59    /// Construct a new plaintext message (used before key exchange).
60    pub fn plaintext(id: MessageId, seq_no: i32, body: Vec<u8>) -> Self {
61        Self { id, seq_no, body }
62    }
63
64    /// Serialize the message into the plaintext wire format:
65    ///
66    /// ```text
67    /// auth_key_id:long  (0 for plaintext)
68    /// message_id:long
69    /// message_data_length:int
70    /// message_data:bytes
71    /// ```
72    pub fn to_plaintext_bytes(&self) -> Vec<u8> {
73        let mut buf = Vec::with_capacity(8 + 8 + 4 + self.body.len());
74        buf.extend(0i64.to_le_bytes()); // auth_key_id = 0
75        buf.extend(self.id.0.to_le_bytes()); // message_id
76        buf.extend((self.body.len() as u32).to_le_bytes()); // length
77        buf.extend(&self.body);
78        buf
79    }
80}