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// Based on layer: https://github.com/ankit-chaubey/layer
8// Follows official Telegram client behaviour (tdesktop, TDLib).
9//
10// If you use or modify this code, keep this notice at the top of your file
11// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
12// https://github.com/ankit-chaubey/ferogram
13
14//! MTProto message framing types.
15
16use std::time::{SystemTime, UNIX_EPOCH};
17
18/// A 64-bit MTProto message identifier.
19///
20/// Per the spec: the lower 32 bits are derived from the current Unix time;
21/// the upper 32 bits are a monotonically increasing counter within the second.
22/// The least significant two bits must be zero for client messages.
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub struct MessageId(pub u64);
25
26impl MessageId {
27    /// Generate a new message ID using the system clock.
28    ///
29    /// Call this from the session rather than directly so that the
30    /// counter is properly sequenced.
31    pub(crate) fn generate(counter: u32) -> Self {
32        let unix_secs = SystemTime::now()
33            .duration_since(UNIX_EPOCH)
34            .unwrap_or_default()
35            .as_secs();
36        // Lower 32 bits = seconds, upper 32 bits = intra-second counter × 4
37        // (the two LSBs must be 0b00 for client messages)
38        let id = (unix_secs << 32) | (u64::from(counter) << 2);
39        Self(id)
40    }
41}
42
43/// A framed MTProto message ready to be sent.
44#[derive(Debug)]
45pub struct Message {
46    /// Unique identifier for this message.
47    pub id: MessageId,
48    /// Session-scoped sequence number (even for content-unrelated, odd for content-related).
49    pub seq_no: i32,
50    /// The serialized TL body (constructor ID + fields).
51    pub body: Vec<u8>,
52}
53
54impl Message {
55    /// Construct a new plaintext message (used before key exchange).
56    pub fn plaintext(id: MessageId, seq_no: i32, body: Vec<u8>) -> Self {
57        Self { id, seq_no, body }
58    }
59
60    /// Serialize the message into the plaintext wire format:
61    ///
62    /// ```text
63    /// auth_key_id:long  (0 for plaintext)
64    /// message_id:long
65    /// message_data_length:int
66    /// message_data:bytes
67    /// ```
68    pub fn to_plaintext_bytes(&self) -> Vec<u8> {
69        let mut buf = Vec::with_capacity(8 + 8 + 4 + self.body.len());
70        buf.extend(0i64.to_le_bytes()); // auth_key_id = 0
71        buf.extend(self.id.0.to_le_bytes()); // message_id
72        buf.extend((self.body.len() as u32).to_le_bytes()); // length
73        buf.extend(&self.body);
74        buf
75    }
76}