1mod adaptor;
31pub mod defs;
32
33#[cfg(test)]
34mod tests;
35
36use std::cmp::Ordering;
37use std::time::Duration;
38
39use defs::Instant; use defs::Key;
42pub use defs::{ChannelState, Gap, MessageBoxes, UpdatesLike, UpdatesStateSnap};
43use defs::{
44 LiveEntry, NO_DATE, NO_PTS, NO_SEQ, POSSIBLE_GAP_TIMEOUT, PossibleGap, PtsInfo, UpdateAndPeers,
45};
46use ferogram_tl_types as tl;
47
48pub fn classify_own_response(body: &[u8]) -> Option<UpdatesLike> {
65 use tl::{Deserializable, Identifiable};
66
67 if body.len() < 4 {
68 return None;
69 }
70
71 let mut cur = tl::Cursor::from_slice(body);
73 if let Ok(updates) = tl::enums::Updates::deserialize(&mut cur) {
74 return Some(UpdatesLike::Updates(Box::new(updates)));
75 }
76
77 let mut cur = tl::Cursor::from_slice(body);
82 if let Ok(tl::enums::messages::ChatInviteJoinResult::Ok(ok)) =
83 tl::enums::messages::ChatInviteJoinResult::deserialize(&mut cur)
84 {
85 return Some(UpdatesLike::Updates(Box::new(ok.updates)));
86 }
87
88 let id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
91 if id == <tl::types::messages::AffectedMessages as Identifiable>::CONSTRUCTOR_ID {
92 let mut cur = tl::Cursor::from_slice(&body[4..]);
93 if let Ok(affected) = tl::types::messages::AffectedMessages::deserialize(&mut cur) {
94 return Some(UpdatesLike::AffectedMessages(affected));
95 }
96 }
97
98 None
99}
100
101fn next_updates_deadline() -> Instant {
104 Instant::now() + defs::NO_UPDATES_TIMEOUT
105}
106
107fn update_sort_key(update: &tl::enums::Update) -> i32 {
108 match PtsInfo::from_update(update) {
109 Some(info) => info.pts - info.count,
110 None => NO_PTS,
111 }
112}
113
114impl Default for MessageBoxes {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl MessageBoxes {
123 pub fn new() -> Self {
125 tracing::trace!("[ferogram::msgbox] created new (no prior state)");
126 Self {
127 entries: Vec::new(),
128 date: NO_DATE,
129 seq: NO_SEQ,
130 getting_diff_for: Vec::new(),
131 channel_diff_in_flight: None,
132 next_deadline: next_updates_deadline(),
133 }
134 }
135
136 pub fn load(state: UpdatesStateSnap) -> Self {
138 tracing::trace!("[ferogram::msgbox] loaded from state: {:?}", state);
139 let mut entries = Vec::with_capacity(2 + state.channels.len());
140 let mut getting_diff_for = Vec::with_capacity(2 + state.channels.len());
141 let deadline = next_updates_deadline();
142
143 if state.pts != NO_PTS {
144 entries.push(LiveEntry {
145 key: Key::Common,
146 pts: state.pts,
147 deadline,
148 possible_gap: None,
149 });
150 }
151 if state.qts != NO_PTS {
152 entries.push(LiveEntry {
153 key: Key::Secondary,
154 pts: state.qts,
155 deadline,
156 possible_gap: None,
157 });
158 }
159 entries.extend(state.channels.iter().map(|c| LiveEntry {
160 key: Key::Channel(c.id),
161 pts: c.pts,
162 deadline,
163 possible_gap: None,
164 }));
165 entries.sort_by_key(|e| e.key);
166
167 getting_diff_for.extend(entries.iter().map(|e| e.key));
169
170 Self {
171 entries,
172 date: state.date,
173 seq: state.seq,
174 getting_diff_for,
175 channel_diff_in_flight: None,
176 next_deadline: deadline,
177 }
178 }
179
180 fn entry(&self, key: Key) -> Option<&LiveEntry> {
181 self.entries
182 .binary_search_by_key(&key, |e| e.key)
183 .map(|i| &self.entries[i])
184 .ok()
185 }
186
187 fn update_entry(&mut self, key: Key, f: impl FnOnce(&mut LiveEntry)) -> bool {
188 match self.entries.binary_search_by_key(&key, |e| e.key) {
189 Ok(i) => {
190 f(&mut self.entries[i]);
191 true
192 }
193 Err(_) => false,
194 }
195 }
196
197 fn force_update_entry(&mut self, mut entry: LiveEntry, f: impl FnOnce(&mut LiveEntry)) {
198 match self.entries.binary_search_by_key(&entry.key, |e| e.key) {
199 Ok(i) => f(&mut self.entries[i]),
200 Err(i) => {
201 f(&mut entry);
202 self.entries.insert(i, entry);
203 }
204 }
205 }
206
207 fn set_entry(&mut self, entry: LiveEntry) {
208 match self.entries.binary_search_by_key(&entry.key, |e| e.key) {
209 Ok(i) => self.entries[i] = entry,
210 Err(i) => self.entries.insert(i, entry),
211 }
212 }
213
214 fn set_pts(&mut self, key: Key, pts: i32) {
215 if !self.update_entry(key, |e| e.pts = pts) {
216 self.set_entry(LiveEntry {
217 key,
218 pts,
219 deadline: next_updates_deadline(),
220 possible_gap: None,
221 });
222 }
223 }
224
225 fn pop_entry(&mut self, key: Key) -> Option<LiveEntry> {
226 match self.entries.binary_search_by_key(&key, |e| e.key) {
227 Ok(i) => Some(self.entries.remove(i)),
228 Err(_) => None,
229 }
230 }
231
232 fn reset_deadline(&mut self, key: Key, deadline: Instant) {
233 let mut old_deadline = self.next_deadline;
234 self.update_entry(key, |e| {
235 old_deadline = e.deadline;
236 e.deadline = deadline;
237 });
238 if self.next_deadline == old_deadline {
239 self.next_deadline = self
240 .entries
241 .iter()
242 .fold(deadline, |d, e| d.min(e.effective_deadline()));
243 }
244 }
245
246 fn reset_timeout(&mut self, key: Key, timeout: Option<i32>) {
247 self.reset_deadline(
248 key,
249 timeout
250 .map(|t| Instant::now() + Duration::from_secs(t as _))
251 .unwrap_or_else(next_updates_deadline),
252 );
253 }
254
255 fn try_begin_get_diff(&mut self, key: Key) {
256 if !self.getting_diff_for.contains(&key) {
257 if self.update_entry(key, |e| e.possible_gap = None) {
258 self.getting_diff_for.push(key);
259 } else {
260 tracing::debug!(
261 "[ferogram::msgbox] begin_get_diff skipped for {:?}: no entry exists",
262 key
263 );
264 }
265 }
266 }
267
268 fn try_end_get_diff(&mut self, key: Key) {
269 let i = match self.getting_diff_for.iter().position(|&k| k == key) {
270 Some(i) => i,
271 None => return,
272 };
273 self.getting_diff_for.remove(i);
274 self.reset_deadline(key, next_updates_deadline());
275 debug_assert!(
276 self.entry(key).is_none_or(|e| e.possible_gap.is_none()),
277 "gaps shouldn't be created while getting difference"
278 );
279 }
280
281 pub fn is_empty(&self) -> bool {
283 self.entries.is_empty()
284 }
285
286 pub fn set_state(&mut self, state: tl::types::updates::State) {
288 debug_assert!(self.is_empty());
289 let deadline = next_updates_deadline();
290 self.set_entry(LiveEntry {
291 key: Key::Common,
292 pts: state.pts,
293 deadline,
294 possible_gap: None,
295 });
296 self.set_entry(LiveEntry {
297 key: Key::Secondary,
298 pts: state.qts,
299 deadline,
300 possible_gap: None,
301 });
302 self.date = state.date;
303 self.seq = state.seq;
304 self.next_deadline = deadline;
305 }
306
307 pub fn try_set_channel_state(&mut self, id: i64, pts: i32) {
309 if self.entry(Key::Channel(id)).is_none() {
310 self.set_entry(LiveEntry {
311 key: Key::Channel(id),
312 pts,
313 deadline: next_updates_deadline(),
314 possible_gap: None,
315 });
316 }
317 }
318
319 pub fn session_state(&self) -> UpdatesStateSnap {
321 UpdatesStateSnap {
322 pts: self.entry(Key::Common).map(|e| e.pts).unwrap_or(NO_PTS),
323 qts: self.entry(Key::Secondary).map(|e| e.pts).unwrap_or(NO_PTS),
324 date: self.date,
325 seq: self.seq,
326 channels: self
327 .entries
328 .iter()
329 .filter_map(|e| match e.key {
330 Key::Channel(id) => Some(ChannelState { id, pts: e.pts }),
331 _ => None,
332 })
333 .collect(),
334 }
335 }
336
337 pub fn check_deadlines(&mut self) -> Instant {
343 let now = Instant::now();
344
345 if !self.getting_diff_for.is_empty() {
346 return now; }
348
349 if now >= self.next_deadline {
350 self.getting_diff_for
352 .extend(self.entries.iter().filter_map(|e| {
353 if now >= e.effective_deadline() {
354 tracing::debug!(
355 "[ferogram::msgbox] deadline met for {:?}; forcing diff",
356 e.key
357 );
358 Some(e.key)
359 } else {
360 None
361 }
362 }));
363
364 for i in 0..self.getting_diff_for.len() {
366 self.update_entry(self.getting_diff_for[i], |e| e.possible_gap = None);
367 }
368
369 if self.getting_diff_for.is_empty() {
370 self.next_deadline = next_updates_deadline();
371 }
372 }
373
374 self.next_deadline
375 }
376
377 pub fn get_difference(&self) -> Option<tl::functions::updates::GetDifference> {
382 for key in [Key::Common, Key::Secondary] {
383 if self.getting_diff_for.contains(&key) {
384 let pts = self
385 .entry(Key::Common)
386 .map(|e| e.pts)
387 .expect("Common entry must exist when diffing it");
388
389 return Some(tl::functions::updates::GetDifference {
390 pts,
391 pts_limit: None,
392 pts_total_limit: None,
393 date: self.date.max(1),
394 qts: self.entry(Key::Secondary).map(|e| e.pts).unwrap_or(NO_PTS),
395 qts_limit: None,
396 });
397 }
398 }
399 None
400 }
401
402 pub fn get_channel_difference(
407 &mut self,
408 ) -> Option<(i64, tl::functions::updates::GetChannelDifference)> {
409 let (key, channel_id) = self.getting_diff_for.iter().find_map(|&k| match k {
414 Key::Channel(id) if self.channel_diff_in_flight != Some(id) => Some((k, id)),
415 _ => None,
416 })?;
417
418 let pts = self
419 .entry(key)
420 .map(|e| e.pts)
421 .expect("Channel entry must exist when diffing it");
422
423 self.channel_diff_in_flight = Some(channel_id);
424
425 Some((
426 channel_id,
427 tl::functions::updates::GetChannelDifference {
428 force: false,
429 channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
430 channel_id,
431 access_hash: 0, }),
433 filter: tl::enums::ChannelMessagesFilter::Empty,
434 pts,
435 limit: 0, },
437 ))
438 }
439}
440
441impl MessageBoxes {
444 pub fn process_updates(&mut self, updates: UpdatesLike) -> Result<UpdateAndPeers, Gap> {
449 let deadline = next_updates_deadline();
450
451 let tl::types::UpdatesCombined {
452 date,
453 seq_start,
454 seq,
455 mut updates,
456 users,
457 chats,
458 } = match adaptor::adapt(updates) {
459 Ok(combined) => combined,
460 Err(Gap) => {
461 self.try_begin_get_diff(Key::Common);
462 return Err(Gap);
463 }
464 };
465
466 let new_date = if date == NO_DATE { self.date } else { date };
467 let new_seq = if seq == NO_SEQ { self.seq } else { seq };
468
469 if seq_start != NO_SEQ {
471 match (self.seq + 1).cmp(&seq_start) {
472 Ordering::Equal => {} Ordering::Greater => {
474 tracing::debug!(
475 "[ferogram::msgbox] duplicate seq (local={}, remote={}), skipping",
476 self.seq,
477 seq_start
478 );
479 return Ok((Vec::new(), users, chats));
480 }
481 Ordering::Less => {
482 tracing::debug!(
483 "[ferogram::msgbox] seq gap (local={}, remote={})",
484 self.seq,
485 seq_start
486 );
487 self.try_begin_get_diff(Key::Common);
488 return Err(Gap);
489 }
490 }
491 }
492
493 updates.sort_by_key(update_sort_key);
495
496 let mut result: Vec<tl::enums::Update> = Vec::with_capacity(updates.len());
497 let mut have_unresolved_gaps = false;
498
499 for update in updates {
500 if let tl::enums::Update::ChannelTooLong(ref u) = update {
502 let key = Key::Channel(u.channel_id);
503 if let Some(pts) = u.pts {
504 self.set_entry(LiveEntry {
505 key,
506 pts,
507 deadline,
508 possible_gap: None,
509 });
510 }
511 self.try_begin_get_diff(key);
512 continue;
513 }
514
515 let info = match PtsInfo::from_update(&update) {
516 Some(info) => info,
517 None => {
518 result.push(update);
520 continue;
521 }
522 };
523
524 if self.getting_diff_for.contains(&info.key) {
527 tracing::debug!(
528 "[ferogram::msgbox] update for {:?} suppressed while getDifference is in flight",
529 info.key
530 );
531 self.reset_deadline(info.key, next_updates_deadline());
532 result.push(update);
533 continue;
534 }
535
536 let mut gap_deadline = None;
537
538 self.force_update_entry(
539 LiveEntry {
540 key: info.key,
541 pts: info.pts - info.count,
542 deadline,
543 possible_gap: None,
544 },
545 |entry| {
546 match (entry.pts + info.count).cmp(&info.pts) {
547 Ordering::Equal => {
548 entry.pts = info.pts;
550 entry.deadline = deadline;
551 result.push(update);
552 }
553 Ordering::Greater => {
554 tracing::debug!(
556 "[ferogram::msgbox] duplicate update for {:?} \
557 (local={}, count={}, remote={})",
558 info.key,
559 entry.pts,
560 info.count,
561 info.pts
562 );
563 }
564 Ordering::Less => {
565 tracing::debug!(
567 "[ferogram::msgbox] gap for {:?} \
568 (local={}, count={}, remote={})",
569 info.key,
570 entry.pts,
571 info.count,
572 info.pts
573 );
574 entry
575 .possible_gap
576 .get_or_insert_with(|| PossibleGap {
577 deadline: Instant::now() + POSSIBLE_GAP_TIMEOUT,
578 updates: Vec::new(),
579 })
580 .updates
581 .push(update.clone());
582 }
583 }
584
585 if let Some(mut gap) = entry.possible_gap.take() {
587 gap.updates.sort_by_key(|u| -update_sort_key(u));
588 while let Some(gap_update) = gap.updates.pop() {
589 let gap_info = PtsInfo::from_update(&gap_update)
590 .expect("only updates with pts may be buffered as gaps");
591 match (entry.pts + gap_info.count).cmp(&gap_info.pts) {
592 Ordering::Equal => {
593 entry.pts = gap_info.pts;
594 result.push(gap_update);
595 }
596 Ordering::Greater => {}
597 Ordering::Less => {
598 gap.updates.push(gap_update);
599 break;
600 }
601 }
602 }
603 if !gap.updates.is_empty() {
604 gap_deadline = Some(gap.deadline);
605 entry.possible_gap = Some(gap);
606 have_unresolved_gaps = true;
607 }
608 }
609 },
610 );
611
612 self.next_deadline = self.next_deadline.min(gap_deadline.unwrap_or(deadline));
613 }
614
615 if !result.is_empty() && !have_unresolved_gaps {
616 self.date = new_date;
617 self.seq = new_seq;
618 }
619
620 Ok((result, users, chats))
621 }
622}
623
624impl MessageBoxes {
627 pub fn apply_difference(
629 &mut self,
630 difference: tl::enums::updates::Difference,
631 ) -> UpdateAndPeers {
632 tracing::trace!("[ferogram::msgbox] applying account difference");
633 if !self.getting_diff_for.contains(&Key::Common)
634 && !self.getting_diff_for.contains(&Key::Secondary)
635 {
636 tracing::warn!(
637 "[ferogram::msgbox] apply_difference called but no diff was pending \
638 (concurrent call already completed?); ignoring"
639 );
640 return (Vec::new(), Vec::new(), Vec::new());
641 }
642
643 let finish: bool;
644 let result = match difference {
645 tl::enums::updates::Difference::Empty(e) => {
646 tracing::debug!(
647 "[ferogram::msgbox] difference empty (date={}, seq={})",
648 e.date,
649 e.seq
650 );
651 finish = true;
652 self.date = e.date;
653 self.seq = e.seq;
654 (Vec::new(), Vec::new(), Vec::new())
655 }
656 tl::enums::updates::Difference::Difference(d) => {
657 tracing::debug!(
658 "[ferogram::msgbox] getDifference: received full difference, applying"
659 );
660 finish = true;
661 self.apply_difference_type(d)
662 }
663 tl::enums::updates::Difference::Slice(tl::types::updates::DifferenceSlice {
664 new_messages,
665 new_encrypted_messages,
666 other_updates,
667 chats,
668 users,
669 intermediate_state: state,
670 }) => {
671 tracing::debug!(
672 "[ferogram::msgbox] getDifference: received slice, will request another round"
673 );
674 finish = false;
675 self.apply_difference_type(tl::types::updates::Difference {
676 new_messages,
677 new_encrypted_messages,
678 other_updates,
679 chats,
680 users,
681 state,
682 })
683 }
684 tl::enums::updates::Difference::TooLong(d) => {
685 tracing::warn!(
686 "[ferogram::msgbox] getDifference returned TooLong (pts={}); resetting to server pts",
687 d.pts
688 );
689 finish = true;
690 self.set_pts(Key::Common, d.pts);
691 (Vec::new(), Vec::new(), Vec::new())
692 }
693 };
694
695 if finish {
696 self.try_end_get_diff(Key::Common);
697 self.try_end_get_diff(Key::Secondary);
698 }
699
700 result
701 }
702
703 fn apply_difference_type(
704 &mut self,
705 tl::types::updates::Difference {
706 new_messages,
707 new_encrypted_messages,
708 other_updates: updates,
709 chats,
710 users,
711 state: tl::enums::updates::State::State(state),
712 }: tl::types::updates::Difference,
713 ) -> UpdateAndPeers {
714 self.date = state.date;
715 self.seq = state.seq;
716 self.set_pts(Key::Common, state.pts);
717 self.set_pts(Key::Secondary, state.qts);
718
719 let us = UpdatesLike::Updates(Box::new(tl::enums::Updates::Updates(tl::types::Updates {
721 updates,
722 users,
723 chats,
724 date: NO_DATE,
725 seq: NO_SEQ,
726 })));
727 let (mut result_updates, users, chats) = self
728 .process_updates(us)
729 .expect("gap detected while applying difference - should not happen");
730
731 let msgs: Vec<tl::enums::Update> = new_messages
733 .into_iter()
734 .map(|msg| {
735 tl::enums::Update::NewMessage(tl::types::UpdateNewMessage {
736 message: msg,
737 pts: NO_PTS,
738 pts_count: 0,
739 })
740 })
741 .chain(new_encrypted_messages.into_iter().map(|msg| {
742 tl::enums::Update::NewEncryptedMessage(tl::types::UpdateNewEncryptedMessage {
743 message: msg,
744 qts: NO_PTS,
745 })
746 }))
747 .collect();
748
749 result_updates.splice(0..0, msgs);
750 (result_updates, users, chats)
751 }
752}
753
754impl MessageBoxes {
757 pub fn apply_channel_difference(
759 &mut self,
760 difference: tl::enums::updates::ChannelDifference,
761 ) -> UpdateAndPeers {
762 let Some(channel_id) = self.channel_diff_in_flight.take() else {
763 tracing::warn!(
764 "[ferogram::msgbox] apply_channel_difference called but no channel diff \
765 was in flight (stale/duplicate response?); ignoring"
766 );
767 return (Vec::new(), Vec::new(), Vec::new());
768 };
769
770 let key = Key::Channel(channel_id);
771 if !self.getting_diff_for.contains(&key) {
772 tracing::debug!(
775 "[ferogram::msgbox] apply_channel_difference: channel {} no longer in \
776 getting_diff_for (stale duplicate response); ignoring",
777 channel_id
778 );
779 return (Vec::new(), Vec::new(), Vec::new());
780 }
781
782 tracing::trace!(
783 "[ferogram::msgbox] applying channel {} difference",
784 channel_id
785 );
786 self.update_entry(key, |e| e.possible_gap = None);
787
788 let Ok(tl::types::updates::ChannelDifference {
789 r#final,
790 pts,
791 timeout,
792 new_messages,
793 other_updates: updates,
794 chats,
795 users,
796 }) = adaptor::adapt_channel_difference(difference)
797 else {
798 self.channel_diff_in_flight = Some(channel_id);
802 self.end_channel_difference(PrematureEndReason::TemporaryServerIssues);
803 return (Vec::new(), Vec::new(), Vec::new());
804 };
805
806 if r#final {
807 tracing::debug!(
808 "[ferogram::msgbox] channel {} diff complete (final=true)",
809 channel_id
810 );
811 self.try_end_get_diff(key);
812 } else {
813 tracing::debug!(
814 "[ferogram::msgbox] channel {} diff slice received; requesting next batch",
815 channel_id
816 );
817 }
818
819 self.set_pts(key, pts);
820
821 let us = UpdatesLike::Updates(Box::new(tl::enums::Updates::Updates(tl::types::Updates {
822 updates,
823 users,
824 chats,
825 date: NO_DATE,
826 seq: NO_SEQ,
827 })));
828 let (mut result_updates, users, chats) = self
829 .process_updates(us)
830 .expect("gap detected while applying channel difference");
831
832 let msgs: Vec<tl::enums::Update> = new_messages
834 .into_iter()
835 .map(|msg| {
836 tl::enums::Update::NewChannelMessage(tl::types::UpdateNewChannelMessage {
837 message: msg,
838 pts: NO_PTS,
839 pts_count: 0,
840 })
841 })
842 .collect();
843 result_updates.splice(0..0, msgs);
844
845 self.reset_timeout(key, timeout);
846
847 (result_updates, users, chats)
848 }
849
850 pub fn abort_difference(&mut self) {
861 for key in [Key::Common, Key::Secondary] {
862 self.update_entry(key, |e| e.possible_gap = None);
863 self.try_end_get_diff(key);
864 }
865 tracing::debug!(
866 "[ferogram::msgbox] getDifference aborted; cleared pending state for Common and Secondary"
867 );
868 }
869
870 pub fn force_reset_common_pts(&mut self, pts: i32, qts: i32, date: i32, seq: i32) {
876 self.set_pts(Key::Common, pts);
877 self.set_pts(Key::Secondary, qts);
878 self.date = date;
879 self.seq = seq;
880 tracing::debug!(
881 "[ferogram::msgbox] force_reset_common_pts: pts={pts}, qts={qts}, seq={seq}"
882 );
883 }
884
885 pub fn end_channel_difference(&mut self, reason: PrematureEndReason) {
886 let Some(channel_id) = self.channel_diff_in_flight.take() else {
887 tracing::warn!(
888 "[ferogram::msgbox] end_channel_difference called but no channel diff \
889 was in flight (already ended? duplicate error path)"
890 );
891 return;
892 };
893 let key = Key::Channel(channel_id);
894 if !self.getting_diff_for.contains(&key) {
895 tracing::debug!(
896 "[ferogram::msgbox] end_channel_difference: channel {} no longer in \
897 getting_diff_for (stale duplicate response); ignoring",
898 channel_id
899 );
900 return;
901 }
902
903 tracing::trace!(
904 "[ferogram::msgbox] ending channel {} diff: {:?}",
905 channel_id,
906 reason
907 );
908
909 match reason {
910 PrematureEndReason::TemporaryServerIssues => {
911 self.update_entry(key, |e| e.possible_gap = None);
912 self.try_end_get_diff(key);
913 }
914 PrematureEndReason::Banned => {
915 self.update_entry(key, |e| e.possible_gap = None);
916 self.try_end_get_diff(key);
917 self.pop_entry(key);
918 }
919 }
920 }
921}
922
923#[derive(Debug)]
925pub enum PrematureEndReason {
926 TemporaryServerIssues,
928 Banned,
930}