Skip to main content

ferogram_mtproto/
message.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferogram: async Telegram MTProto client in Rust
5// https://github.com/ankit-chaubey/ferogram
6//
7//
8// If you use or modify this code, keep this notice at the top of your file
9// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
10// https://github.com/ankit-chaubey/ferogram
11
12//! MTProto message framing types.
13
14use std::sync::atomic::{AtomicU32, Ordering};
15use std::time::{SystemTime, UNIX_EPOCH};
16
17/// Process-wide monotonically increasing counter for plaintext message IDs.
18///
19/// A global atomic ensures msg_id monotonicity across concurrent sessions.
20static GLOBAL_MSG_COUNTER: AtomicU32 = AtomicU32::new(1);
21
22/// A 64-bit MTProto message identifier.
23///
24/// Per the spec: the lower 32 bits are derived from the current Unix time;
25/// the upper 32 bits are a monotonically increasing counter within the second.
26/// The least significant two bits must be zero for client messages.
27#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28pub struct MessageId(pub u64);
29
30impl MessageId {
31    /// Generate a new message ID using the system clock and a global counter.
32    ///
33    /// MTProto msg_id layout:
34    ///   bits 63–32: Unix timestamp in seconds (upper 32 bits)
35    ///   bits 31–2:  intra-second sequencing counter (lower 30 bits, × 4)
36    ///   bits 1–0:   must be 0b00 for client messages
37    ///
38    pub(crate) fn generate(_counter: u32) -> Self {
39        let unix_secs = SystemTime::now()
40            .duration_since(UNIX_EPOCH)
41            .unwrap_or_default()
42            .as_secs();
43        // Global atomic: wrapping_add ensures monotonicity across concurrent sessions.
44        let counter = GLOBAL_MSG_COUNTER.fetch_add(1, Ordering::Relaxed);
45        // upper 32 bits = seconds, lower 30 bits = counter × 4 (bits 1-0 = 0b00)
46        let id = (unix_secs << 32) | (u64::from(counter) << 2);
47        Self(id)
48    }
49}
50
51/// A framed MTProto message ready to be sent.
52#[derive(Debug)]
53pub struct Message {
54    /// Unique identifier for this message.
55    pub id: MessageId,
56    /// Session-scoped sequence number (even for content-unrelated, odd for content-related).
57    pub seq_no: i32,
58    /// The serialized TL body (constructor ID + fields).
59    pub body: Vec<u8>,
60}
61
62impl Message {
63    /// Construct a new plaintext message (used before key exchange).
64    pub fn plaintext(id: MessageId, seq_no: i32, body: Vec<u8>) -> Self {
65        Self { id, seq_no, body }
66    }
67
68    /// Serialize the message into the plaintext wire format:
69    ///
70    /// ```text
71    /// auth_key_id:long  (0 for plaintext)
72    /// message_id:long
73    /// message_data_length:int
74    /// message_data:bytes
75    /// ```
76    pub fn to_plaintext_bytes(&self) -> Vec<u8> {
77        let mut buf = Vec::with_capacity(8 + 8 + 4 + self.body.len());
78        buf.extend(0i64.to_le_bytes()); // auth_key_id = 0
79        buf.extend(self.id.0.to_le_bytes()); // message_id
80        buf.extend((self.body.len() as u32).to_le_bytes()); // length
81        buf.extend(&self.body);
82        buf
83    }
84}