Skip to main content

ferogram_msgbox/
defs.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::Duration;
14
15// Instant (real vs test-time fake)
16
17#[cfg(not(test))]
18pub(super) use std::time::Instant;
19
20/// Thread-local fake clock used in tests so we never need `thread::sleep`.
21///
22/// The production code in mod.rs simply calls `Instant::now()`; under
23/// `cfg(test)` that resolves to the controlled fake below.
24#[cfg(test)]
25pub(super) mod fake_clock {
26    use std::cell::RefCell;
27    use std::ops::Add;
28    use std::time::Duration;
29
30    thread_local! {
31        static NOW: RefCell<Duration> = const { RefCell::new(Duration::ZERO) };
32    }
33
34    /// A fake `Instant` backed by a thread-local counter.
35    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
36    pub struct Instant(pub Duration);
37
38    impl Instant {
39        pub fn now() -> Self {
40            NOW.with_borrow(|d| Self(*d))
41        }
42
43        /// Helper used only in tests to observe the raw duration.
44        pub fn elapsed_secs(&self) -> u64 {
45            self.0.as_secs()
46        }
47    }
48
49    impl Add<Duration> for Instant {
50        type Output = Instant;
51        fn add(self, rhs: Duration) -> Instant {
52            Instant(self.0 + rhs)
53        }
54    }
55
56    /// Reset the fake clock to zero.  Call at the start of every test.
57    pub fn reset_time() {
58        NOW.with_borrow_mut(|d| *d = Duration::ZERO);
59    }
60
61    /// Advance the fake clock by `dur`.
62    pub fn advance_time_by(dur: Duration) {
63        NOW.with_borrow_mut(|d| *d += dur);
64    }
65}
66
67#[cfg(test)]
68pub(super) use fake_clock::Instant;
69
70// Allow reader_loop code (which calls `check_deadlines().into()`) to compile
71// under `cfg(test)`.  The reader loop never actually runs in unit-tests, so
72// this conversion just returns a reasonable stand-in.
73#[cfg(test)]
74impl From<fake_clock::Instant> for tokio::time::Instant {
75    fn from(i: fake_clock::Instant) -> Self {
76        tokio::time::Instant::now()
77            .checked_add(i.0)
78            .unwrap_or_else(tokio::time::Instant::now)
79    }
80}
81
82use ferogram_tl_types as tl;
83
84/// Telegram sends `seq` equal to `0` when "it doesn't matter", so we use that value too.
85pub(super) const NO_SEQ: i32 = 0;
86
87/// `qts` of `0` means "ordering should be ignored" for that update.
88pub(super) const NO_PTS: i32 = 0;
89
90/// Sentinel `date` value when constructing dummy Updates containers.
91pub(super) const NO_DATE: i32 = 0;
92
93/// Wait up to 0.5 s before declaring a gap a real gap.
94pub(super) const POSSIBLE_GAP_TIMEOUT: Duration = Duration::from_millis(500);
95
96/// After how long without updates the client will proactively fetch updates.
97///
98/// Documentation recommends 15 minutes without updates.
99pub(super) const NO_UPDATES_TIMEOUT: Duration = Duration::from_secs(15 * 60);
100
101// Keys
102
103/// A sortable message-box entry key.
104#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
105pub(crate) enum Key {
106    Common,
107    Secondary,
108    Channel(i64),
109}
110
111// Live entry
112
113/// A single live entry inside [`MessageBoxes`].
114#[derive(Debug)]
115pub(super) struct LiveEntry {
116    pub(super) key: Key,
117    pub(super) pts: i32,
118    /// Next instant when we forcibly fetch difference if no updates arrived by then.
119    pub(super) deadline: Instant,
120    /// If set, we detected a possible gap and are waiting to see if it resolves itself.
121    pub(super) possible_gap: Option<PossibleGap>,
122}
123
124impl LiveEntry {
125    pub(super) fn effective_deadline(&self) -> Instant {
126        match &self.possible_gap {
127            Some(gap) => gap.deadline.min(self.deadline),
128            None => self.deadline,
129        }
130    }
131}
132
133// PossibleGap
134
135#[derive(Debug)]
136pub(super) struct PossibleGap {
137    pub(super) deadline: Instant,
138    /// Pending updates (those with a higher pts that are creating the gap).
139    pub(super) updates: Vec<tl::enums::Update>,
140}
141
142// MessageBoxes (container)
143
144/// All live message boxes.  The single authority for update-gap detection.
145///
146/// See <https://core.telegram.org/api/updates#message-related-event-sequences>.
147#[derive(Debug)]
148pub struct MessageBoxes {
149    /// Live entries sorted by key.
150    pub(super) entries: Vec<LiveEntry>,
151
152    pub(super) date: i32,
153    pub(super) seq: i32,
154
155    /// Entries for which we must currently fetch difference.
156    pub(super) getting_diff_for: Vec<Key>,
157
158    /// Channel diff request currently in flight (RPC sent, response not yet
159    /// applied).  Distinct from `getting_diff_for`, which controls update
160    /// suppression and survives across the RTT.  This flag prevents
161    /// `get_channel_difference()` from handing out a second concurrent
162    /// request for the same channel (e.g. across a reconnect generation
163    /// boundary), and is cleared whenever the response (or its premature-end
164    /// equivalent) is consumed - by `apply_channel_difference` or
165    /// `end_channel_difference` - regardless of which generation issued it.
166    pub(super) channel_diff_in_flight: Option<i64>,
167
168    /// Cached minimum deadline across all entries.
169    pub(super) next_deadline: Instant,
170}
171
172// PtsInfo - per-update pts metadata
173
174#[derive(Debug)]
175pub(super) struct PtsInfo {
176    pub(super) key: Key,
177    pub(super) pts: i32,
178    pub(super) count: i32,
179}
180
181// Gap error
182
183/// Returned by [`MessageBoxes::process_updates`] when a gap is detected.
184#[derive(Debug, PartialEq, Eq)]
185pub struct Gap;
186
187// UpdatesLike
188
189/// Anything that should be treated as an update batch.
190#[derive(Debug)]
191pub enum UpdatesLike {
192    /// Normal push update from the socket.
193    Updates(Box<tl::enums::Updates>),
194    /// The connection was closed; a gap may now exist.
195    ConnectionClosed,
196    /// A received update could not be parsed (unknown constructor, truncation).
197    MalformedUpdates,
198    /// RPC response for `messages.deleteMessages` / `messages.readHistory` etc.
199    AffectedMessages(tl::types::messages::AffectedMessages),
200    /// Same as above but channel-specific.
201    AffectedChannelMessages {
202        affected: tl::types::messages::AffectedMessages,
203        channel_id: i64,
204    },
205    /// updateShortSentMessage confirmed; request_body used to reconstruct the outgoing message.
206    /// If body is None or not a SendMessage, advances pts silently.
207    SentMessage {
208        pts: i32,
209        pts_count: i32,
210        request_body: Option<Vec<u8>>,
211        update: Box<tl::types::UpdateShortSentMessage>,
212    },
213}
214
215// Public update state types (for persisting)
216
217/// Per-channel pts snapshot.
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct ChannelState {
220    pub id: i64,
221    pub pts: i32,
222}
223
224/// Full snapshot of the update state for session persistence.
225#[derive(Debug, Clone, Default, PartialEq, Eq)]
226pub struct UpdatesStateSnap {
227    pub pts: i32,
228    pub qts: i32,
229    pub date: i32,
230    pub seq: i32,
231    pub channels: Vec<ChannelState>,
232}
233
234// Pair type
235
236pub type UpdateAndPeers = (
237    Vec<tl::enums::Update>,
238    Vec<tl::enums::User>,
239    Vec<tl::enums::Chat>,
240);