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);