Skip to main content

ferogram_mtproto/
message.rs

1/*
2 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
3 * https://github.com/ankit-chaubey
4 *
5 * Project: ferogram
6 * Website: https://ferogram.dev
7 *
8 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
9 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
10 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
11 * This file may not be copied, modified, or distributed except according
12 * to those terms.
13 */
14
15use std::time::{SystemTime, UNIX_EPOCH};
16
17/// A 64-bit MTProto message identifier.
18///
19/// Per the spec: the lower 32 bits are derived from the current Unix time;
20/// the upper 32 bits are a monotonically increasing counter within the second.
21/// The least significant two bits must be zero for client messages.
22#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
23pub struct MessageId(pub u64);
24
25impl MessageId {
26    /// Generate a new message ID using the system clock and the session-local counter.
27    ///
28    /// MTProto msg_id layout:
29    ///   bits 63-32: Unix timestamp in seconds (upper 32 bits)
30    ///   bits 31-2:  intra-second sequencing counter (lower 30 bits, × 4)
31    ///   bits 1-0:   must be 0b00 for client messages
32    ///
33    /// The previous implementation accepted a `_counter` parameter but silently
34    /// ignored it, routing all calls through a process-wide `GLOBAL_MSG_COUNTER`.
35    /// The session-local `msg_counter` in `Session` was incremented uselessly.
36    /// Uses the caller-supplied `counter` directly so each `Session` instance
37    /// drives its own monotonic sequence without a global side-channel.
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        // upper 32 bits = seconds, lower 30 bits = counter × 4 (bits 1-0 = 0b00)
44        let id = (unix_secs << 32) | (u64::from(counter) << 2);
45        Self(id)
46    }
47}
48
49/// A framed MTProto message ready to be sent.
50#[derive(Debug)]
51pub struct Message {
52    /// Unique identifier for this message.
53    pub id: MessageId,
54    /// Session-scoped sequence number (even for content-unrelated, odd for content-related).
55    pub seq_no: i32,
56    /// The serialized TL body (constructor ID + fields).
57    pub body: Vec<u8>,
58}
59
60impl Message {
61    /// Construct a new plaintext message (used before key exchange).
62    pub fn plaintext(id: MessageId, seq_no: i32, body: Vec<u8>) -> Self {
63        Self { id, seq_no, body }
64    }
65
66    /// Serialize the message into the plaintext wire format:
67    ///
68    /// ```text
69    /// auth_key_id:long  (0 for plaintext)
70    /// message_id:long
71    /// message_data_length:int
72    /// message_data:bytes
73    /// ```
74    pub fn to_plaintext_bytes(&self) -> Vec<u8> {
75        let mut buf = Vec::with_capacity(8 + 8 + 4 + self.body.len());
76        buf.extend(0i64.to_le_bytes()); // auth_key_id = 0
77        buf.extend(self.id.0.to_le_bytes()); // message_id
78        buf.extend((self.body.len() as u32).to_le_bytes()); // length
79        buf.extend(&self.body);
80        buf
81    }
82}