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