1pub mod fragment;
18pub mod repair;
19pub mod sequence;
20
21use heapless::{Deque, FnvIndexMap};
22use umsh_core::PublicKey;
23
24use crate::ParseError;
25use crate::codec;
26use crate::model::{
27 ConversationKey, FRAGMENT_BODY_MAX, FRAGMENT_COUNT_MAX, Fragment, MessageSequence, MessageType,
28 REASSEMBLED_BODY_MAX, Regarding, SenderScope, TextMessage,
29};
30use crate::validate::{self, Envelope, TextProfile, ValidateError, Validated};
31
32use fragment::{FragmentPlan, InsertOutcome, ReassemblyPool, RenderSentinels, empty_slot};
33use repair::{CoalesceRing, JitterSource, PendingLookup};
34use sequence::{
35 InboundStream, MessageHandle, OutboundStream, PendingRepair, SerialClass, StreamKey, classify,
36};
37
38pub const MAX_FRAME: usize = 240;
40
41const ARENA_SIZE: usize = 4096;
42
43#[derive(Clone, Debug)]
45pub struct EngineConfig {
46 pub reorder_grace_ms: u64,
48 pub fragment_grace_ms: u64,
54 pub group_jitter_ms: u64,
56 pub max_auto_repair_gap: u8,
58 pub min_request_interval_ms: u64,
60 pub max_requests_per_tick: u8,
62 pub max_repair_attempts: u8,
64 pub reassembly_ttl_ms: u64,
66 pub request_retry_ms: u64,
68 pub coalesce_window_ms: u64,
70 pub fragment_notify_ms: u64,
73 pub sentinels: RenderSentinels,
74}
75
76impl Default for EngineConfig {
77 fn default() -> Self {
78 Self {
79 reorder_grace_ms: 2_000,
80 fragment_grace_ms: 8_000,
81 group_jitter_ms: 4_000,
82 max_auto_repair_gap: 8,
83 min_request_interval_ms: 2_000,
84 max_requests_per_tick: 4,
85 max_repair_attempts: 4,
86 reassembly_ttl_ms: 90_000,
87 request_retry_ms: 8_000,
88 coalesce_window_ms: 10_000,
89 fragment_notify_ms: 30_000,
90 sentinels: RenderSentinels::default(),
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum Destination {
98 Peer(PublicKey),
100 Channel(umsh_core::ChannelTag),
102 ChannelPeer {
104 channel: umsh_core::ChannelTag,
105 peer: PublicKey,
106 },
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub struct ArchiveKey {
112 pub conversation: ConversationKey,
113 pub message_id: u8,
114 pub fragment: Option<u8>,
115}
116
117#[derive(Clone, Debug)]
119pub struct Transmission {
120 pub transmission_id: u32,
121 pub destination: Destination,
122 pub archive: Option<ArchiveKey>,
125 pub payload: heapless::Vec<u8, MAX_FRAME>,
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub struct BodyRef {
133 offset: u16,
134 len: u16,
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub enum ResolvedRef {
140 Handle(MessageHandle),
141 Unresolved(crate::model::WireRef),
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum Direction {
146 Inbound,
147 Outbound,
148}
149
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum Presence {
157 Present,
159 GapPending,
161 Unavailable,
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub enum CompletionStatus {
169 Complete,
170 Partial {
171 present: u16,
173 count: u8,
174 finalized: bool,
176 },
177}
178
179#[derive(Clone, Copy, Debug)]
181pub struct MessageMutation {
182 pub handle: MessageHandle,
183 pub revision: u32,
186 pub kind: MutationKind,
187}
188
189#[derive(Clone, Copy, Debug)]
190pub enum MutationKind {
191 Insert {
193 conversation: ConversationKey,
194 sender: SenderScope,
195 direction: Direction,
196 message_type: MessageType,
197 wire_id: Option<u8>,
198 epoch: u16,
199 client_token: Option<u32>,
201 sender_handle: Option<BodyRef>,
202 regarding: Option<ResolvedRef>,
203 bg_color: Option<[u8; 3]>,
204 text_color: Option<[u8; 3]>,
205 body: BodyRef,
206 status: CompletionStatus,
207 presence: Presence,
209 late: bool,
212 notify: bool,
216 },
217 UpdateBody {
219 body: BodyRef,
220 status: CompletionStatus,
221 late: bool,
223 notify: bool,
225 },
226 Edit {
228 conversation: ConversationKey,
229 original: ResolvedRef,
230 body: BodyRef,
231 },
232 Delete {
234 conversation: ConversationKey,
235 original: ResolvedRef,
236 },
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq)]
240pub enum DeliveryState {
241 Sent,
242 Acked,
243 Failed,
244}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum RepairOutcome {
248 Repaired,
249 Unavailable,
250 Exhausted,
251 Expired,
252 Unaddressable,
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
257pub enum Event {
258 DeliveryStateChanged {
259 handle: MessageHandle,
260 fragment: Option<u8>,
261 state: DeliveryState,
262 },
263 RepairStarted {
264 conversation: ConversationKey,
265 sender: SenderScope,
266 message_id: u8,
267 fragment: Option<u8>,
268 },
269 RepairFinished {
270 conversation: ConversationKey,
271 sender: SenderScope,
272 message_id: u8,
273 outcome: RepairOutcome,
274 },
275 MessageUnavailable {
277 conversation: ConversationKey,
278 sender: SenderScope,
279 message_id: u8,
280 fragment: Option<u8>,
281 },
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq)]
286pub enum Diagnostic {
287 ParseFailed(ParseError),
288 ValidateFailed(ValidateError),
289 DuplicateMessage {
290 message_id: u8,
291 },
292 DuplicateFragment {
293 message_id: u8,
294 fragment: u8,
295 },
296 FragmentConflict {
298 message_id: u8,
299 fragment: u8,
300 },
301 OversizedFragment {
303 message_id: u8,
304 fragment: u8,
305 },
306 FragmentCountExceeded {
308 message_id: u8,
309 count: u8,
310 },
311 FragmentCountMismatch {
313 message_id: u8,
314 },
315 HintCollision {
318 conversation: ConversationKey,
319 },
320 ReassemblyEvicted {
322 message_id: u8,
323 },
324 StreamEvicted,
326 RepeatedPresentationOptions {
328 mask: u16,
329 },
330 IgnoredContinuationMetadata {
332 message_id: u8,
333 },
334 OutputOverflow,
336 UnknownLookup {
338 request_id: u32,
339 },
340 ReassembledInvalidUtf8 {
344 message_id: u8,
345 },
346 CoalescedResend {
349 message_id: u8,
350 },
351 UnattributableResend,
353}
354
355#[derive(Clone, Debug)]
357pub enum Output {
358 Transmit(Transmission),
359 StoreCheckpoint {
368 conversation: ConversationKey,
369 next_id: u8,
370 epoch: u16,
371 },
372 LookupOutbound {
375 request_id: u32,
376 conversation: ConversationKey,
377 sequence: MessageSequence,
378 },
379 StoreArchive {
384 key: ArchiveKey,
385 payload: heapless::Vec<u8, MAX_FRAME>,
386 },
387 DeleteArchive {
393 conversation: ConversationKey,
394 message_id: u8,
395 },
396 StoreMessage(MessageMutation),
397 Event(Event),
398 Diagnostic(Diagnostic),
399}
400
401#[derive(Clone, Copy, Debug)]
403pub enum ArchiveResult<'a> {
404 Found {
406 payload: &'a [u8],
407 },
408 Deleted,
409 Evicted,
410 Unknown,
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
415pub struct StreamCheckpoint {
416 pub conversation: ConversationKey,
417 pub next_id: u8,
418 pub epoch: u16,
419}
420
421#[derive(Clone, Copy, Debug)]
423pub enum ComposeIntent<'a> {
424 Text { body: &'a str, status: bool },
426 Reply {
430 body: &'a str,
431 regarding: RegardingRef,
432 status: bool,
433 },
434 Edit { original: ComposeRef, body: &'a str },
436 Delete { original: ComposeRef },
438}
439
440#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442pub enum ComposeRef {
443 Handle(MessageHandle),
445 Wire { message_id: u8, epoch: u16 },
451}
452
453#[derive(Clone, Copy, Debug, PartialEq, Eq)]
462pub enum RegardingRef {
463 Handle(MessageHandle),
465 Wire {
473 message_id: u8,
474 direction: Direction,
475 sender_hint: Option<umsh_core::NodeHint>,
478 epoch: Option<u16>,
479 },
480}
481
482fn regarding_sender(
484 conversation: ConversationKey,
485 direction: Direction,
486 sender_hint: Option<umsh_core::NodeHint>,
487) -> Option<SenderScope> {
488 match direction {
489 Direction::Outbound => Some(SenderScope::Local),
490 Direction::Inbound => match conversation {
491 ConversationKey::Direct { peer }
492 | ConversationKey::ChannelDirect { peer, .. }
493 | ConversationKey::Room { room: peer } => Some(SenderScope::Peer(peer)),
494 ConversationKey::ChannelGroup { .. } => sender_hint.map(SenderScope::ClaimedMember),
495 },
496 }
497}
498
499#[derive(Clone, Copy, Debug, PartialEq, Eq)]
500pub enum ComposeError {
501 UnknownOriginal,
503 UnknownRegarding,
505 TooLarge,
507 Encode(crate::EncodeError),
508}
509
510#[derive(Clone, Copy, Debug, PartialEq, Eq)]
511pub enum ReceiveError {
512 Parse(ParseError),
513 Validate(ValidateError),
514}
515
516pub struct Engine<P: TextProfile, const SLOTS: usize = 4, const PAGES: usize = 24> {
521 profile: P,
522 config: EngineConfig,
523 local_key: PublicKey,
524 jitter: JitterSource,
525 outbound: FnvIndexMap<ConversationKey, OutboundStream, 8>,
526 cold_checkpoints: heapless::Vec<(ConversationKey, u8, u16), 24>,
530 inbound: FnvIndexMap<StreamKey, InboundStream, 16>,
531 pool: ReassemblyPool<SLOTS, PAGES>,
532 outputs: Deque<Output, 32>,
533 arena: [u8; ARENA_SIZE],
534 arena_used: usize,
535 next_handle: u32,
536 next_transmission: u32,
537 next_request: u32,
538 revision: u32,
539 local_handle: heapless::String<24>,
546 lookups: heapless::Vec<PendingLookup, 8>,
547 coalesce: CoalesceRing,
548 in_flight: heapless::Vec<InFlightFrame, 16>,
549 saw_transmit_report: bool,
553 lost_outputs: u32,
554}
555
556#[derive(Clone, Copy, Debug)]
559struct InFlightFrame {
560 transmission_id: u32,
561 handle: MessageHandle,
562 fragment: Option<u8>,
563 archive: Option<ArchiveKey>,
566 expects_ack: bool,
571}
572
573impl<P: TextProfile, const SLOTS: usize, const PAGES: usize> Engine<P, SLOTS, PAGES> {
574 pub fn new(profile: P, local_key: PublicKey, config: EngineConfig, jitter_seed: u64) -> Self {
577 Self {
578 profile,
579 config,
580 local_key,
581 jitter: JitterSource::new(jitter_seed),
582 outbound: FnvIndexMap::new(),
583 cold_checkpoints: heapless::Vec::new(),
584 inbound: FnvIndexMap::new(),
585 pool: ReassemblyPool::new(),
586 outputs: Deque::new(),
587 arena: [0; ARENA_SIZE],
588 arena_used: 0,
589 next_handle: 1,
590 next_transmission: 1,
591 next_request: 1,
592 revision: 1,
593 local_handle: heapless::String::new(),
594 lookups: heapless::Vec::new(),
595 coalesce: CoalesceRing::default(),
596 in_flight: heapless::Vec::new(),
597 saw_transmit_report: false,
598 lost_outputs: 0,
599 }
600 }
601
602 pub fn poll_output(&mut self) -> Option<Output> {
605 let output = self.outputs.pop_front();
606 if output.is_none() {
607 self.arena_used = 0;
608 }
609 output
610 }
611
612 pub fn set_local_handle(&mut self, handle: &str) {
618 self.local_handle.clear();
619 for character in handle.chars() {
620 if self.local_handle.push(character).is_err() {
621 break;
622 }
623 }
624 }
625
626 pub fn body(&self, body: &BodyRef) -> &str {
628 let start = body.offset as usize;
629 let end = start + body.len as usize;
630 core::str::from_utf8(&self.arena[start..end]).unwrap_or("")
631 }
632
633 pub fn lost_outputs(&self) -> u32 {
636 self.lost_outputs
637 }
638
639 pub fn restore(&mut self, checkpoints: &[StreamCheckpoint], _now_ms: u64) {
654 for checkpoint in checkpoints {
655 self.stash_checkpoint(
656 checkpoint.conversation,
657 checkpoint.next_id,
658 checkpoint.epoch,
659 );
660 }
661 }
662
663 pub fn compose(
666 &mut self,
667 conversation: ConversationKey,
668 client_token: u32,
669 intent: ComposeIntent<'_>,
670 now_ms: u64,
671 ) -> Result<MessageHandle, ComposeError> {
672 let (body, status_type, regarding_target, edit_original) = match intent {
673 ComposeIntent::Text { body, status } => (body, status, None, None),
674 ComposeIntent::Reply {
675 body,
676 status,
677 regarding,
678 } => (body, status, Some(regarding), None),
679 ComposeIntent::Edit { original, body } => (body, false, None, Some(original)),
680 ComposeIntent::Delete { original } => ("", false, None, Some(original)),
681 };
682 if body.len() > REASSEMBLED_BODY_MAX {
683 return Err(ComposeError::TooLarge);
684 }
685
686 self.ensure_outbound(conversation, now_ms);
690 let (regarding, regarding_ref) = match regarding_target {
692 None => (None, None),
693 Some(target) => {
694 let (wire, resolved) = self.resolve_regarding(conversation, target)?;
695 (Some(wire), Some(resolved))
696 }
697 };
698 let editing = match edit_original {
699 None => None,
700 Some(ComposeRef::Handle(handle)) => {
701 let stream = self.outbound.get(&conversation).expect("just ensured");
702 Some(
703 stream
704 .refs
705 .lookup_handle(handle)
706 .ok_or(ComposeError::UnknownOriginal)?,
707 )
708 }
709 Some(ComposeRef::Wire { message_id, epoch }) => {
710 let stream = self.outbound.get(&conversation).expect("just ensured");
711 if stream.epoch != epoch || stream.announce_reset {
712 return Err(ComposeError::UnknownOriginal);
716 }
717 let delta = stream.next_id.wrapping_sub(message_id);
721 if delta == 0 || delta > 128 {
722 return Err(ComposeError::UnknownOriginal);
723 }
724 Some(message_id)
725 }
726 };
727
728 let stream = self.outbound.get_mut(&conversation).expect("just ensured");
729 stream.last_active_ms = now_ms;
730 let message_id = stream.allocate();
731 let announce_reset = core::mem::take(&mut stream.announce_reset);
732 let next_id = stream.next_id;
733 let epoch = stream.epoch;
734
735 self.push_output(Output::StoreCheckpoint {
737 conversation,
738 next_id,
739 epoch,
740 });
741
742 let message_type = if status_type {
743 MessageType::Status
744 } else {
745 MessageType::Basic
746 };
747 let handle_bytes: heapless::String<24> = match conversation {
753 ConversationKey::ChannelGroup { .. } => self.local_handle.clone(),
754 _ => heapless::String::new(),
755 };
756 let sender_handle = (!handle_bytes.is_empty()).then(|| handle_bytes.as_str());
757 let template = TextMessage {
758 message_type,
759 sender_handle,
760 sequence: Some(MessageSequence::unfragmented(message_id)),
761 sequence_reset: announce_reset,
762 regarding,
763 editing,
764 bg_color: None,
765 text_color: None,
766 channel_group_resend: false,
767 extensions: Default::default(),
768 body: body.as_bytes(),
769 };
770
771 let handle = self.alloc_handle();
772 self.encode_and_queue(conversation, handle, &template, body.as_bytes(), message_id)?;
773
774 let stream = self.outbound.get_mut(&conversation).expect("present");
778 match editing {
779 None => stream.refs.record(message_id, handle),
780 Some(original_id) => stream.edit_refs.record(message_id, original_id),
781 }
782
783 if let Some(original_id) = editing {
790 self.push_output(Output::DeleteArchive {
791 conversation,
792 message_id: original_id,
793 });
794 if !body.is_empty() {
795 self.archive_replacement(conversation, original_id, body.as_bytes());
796 }
797 }
798
799 let body_ref = self
801 .arena_store(body)
802 .unwrap_or(BodyRef { offset: 0, len: 0 });
803 let original_ref = edit_original.map(|original| match original {
807 ComposeRef::Handle(handle) => ResolvedRef::Handle(handle),
808 ComposeRef::Wire { message_id, .. } => {
809 ResolvedRef::Unresolved(crate::model::WireRef::SenderScoped {
810 sender: SenderScope::Local,
811 message_id,
812 })
813 }
814 });
815 let kind = match (original_ref, body.is_empty()) {
816 (Some(original), true) => MutationKind::Delete {
817 conversation,
818 original,
819 },
820 (Some(original), false) => MutationKind::Edit {
821 conversation,
822 original,
823 body: body_ref,
824 },
825 (None, _) => MutationKind::Insert {
826 conversation,
827 sender: SenderScope::Local,
828 direction: Direction::Outbound,
829 message_type,
830 wire_id: Some(message_id),
831 epoch,
832 client_token: Some(client_token),
833 sender_handle: None,
834 regarding: regarding_ref,
835 bg_color: None,
836 text_color: None,
837 body: body_ref,
838 status: CompletionStatus::Complete,
839 presence: Presence::Present,
840 late: false,
841 notify: false,
842 },
843 };
844 self.emit_mutation(handle, kind);
845 Ok(handle)
846 }
847
848 pub fn receive(
855 &mut self,
856 envelope: &Envelope,
857 sender_full_key: Option<PublicKey>,
858 payload: &[u8],
859 now_ms: u64,
860 ) -> Result<(), ReceiveError> {
861 let (message, info) = codec::parse_with_info(payload).map_err(|error| {
862 self.push_output(Output::Diagnostic(Diagnostic::ParseFailed(error)));
863 ReceiveError::Parse(error)
864 })?;
865 let (validated, notes) = validate::validate(&self.profile, envelope, &message, &info)
866 .map_err(|error| {
867 self.push_output(Output::Diagnostic(Diagnostic::ValidateFailed(error)));
868 ReceiveError::Validate(error)
869 })?;
870 if notes.repeated_presentation_mask != 0 {
871 self.push_output(Output::Diagnostic(
872 Diagnostic::RepeatedPresentationOptions {
873 mask: notes.repeated_presentation_mask,
874 },
875 ));
876 }
877
878 match validated {
879 Validated::Content(content) => {
880 if notes.ignored_continuation_metadata
881 && let Some(sequence) = content.sequence
882 {
883 self.push_output(Output::Diagnostic(
884 Diagnostic::IgnoredContinuationMetadata {
885 message_id: sequence.message_id,
886 },
887 ));
888 }
889 self.receive_content(envelope, sender_full_key, &content, now_ms);
890 }
891 Validated::ResendRequest {
892 sequence,
893 channel_group,
894 } => self.receive_resend_request(envelope, sequence, channel_group, now_ms),
895 Validated::Unavailable { sequence } => {
896 self.receive_unavailable(envelope, sequence, now_ms)
897 }
898 }
899 Ok(())
900 }
901
902 pub fn transmit_update(&mut self, transmission_id: u32, state: DeliveryState, now_ms: u64) {
904 self.saw_transmit_report = true;
905 let Some(position) = self
906 .in_flight
907 .iter()
908 .position(|frame| frame.transmission_id == transmission_id)
909 else {
910 return;
911 };
912 let frame = self.in_flight[position];
913 self.push_output(Output::Event(Event::DeliveryStateChanged {
914 handle: frame.handle,
915 fragment: frame.fragment,
916 state,
917 }));
918 let terminal = match state {
924 DeliveryState::Acked | DeliveryState::Failed => true,
925 DeliveryState::Sent => !frame.expects_ack,
926 };
927 if terminal {
928 self.in_flight.remove(position);
929 }
930 let _ = now_ms;
931 }
932
933 pub fn archive_result(&mut self, request_id: u32, result: ArchiveResult<'_>, now_ms: u64) {
935 let Some(position) = self
936 .lookups
937 .iter()
938 .position(|lookup| lookup.request_id == request_id)
939 else {
940 self.push_output(Output::Diagnostic(Diagnostic::UnknownLookup { request_id }));
941 return;
942 };
943 let lookup = self.lookups.remove(position);
944 let destination = destination_for(&lookup.conversation);
945
946 match result {
947 ArchiveResult::Found { payload } if payload.len() <= MAX_FRAME => {
948 let mut frame = heapless::Vec::new();
949 let _ = frame.extend_from_slice(payload);
950 let track = self
956 .outbound
957 .get(&lookup.conversation)
958 .and_then(|stream| stream.refs.lookup(lookup.sequence.message_id))
959 .map(|handle| {
960 (
961 handle,
962 lookup.sequence.fragment.map(|fragment| fragment.index),
963 )
964 });
965 self.queue_transmit(destination, None, frame, track);
966 }
967 _ => {
968 let response = TextMessage {
971 message_type: MessageType::MessageUnavailable,
972 sequence: Some(lookup.sequence),
973 ..TextMessage::basic("")
974 };
975 let mut buffer = [0u8; MAX_FRAME];
976 if let Ok(len) = codec::encode(&response, &mut buffer) {
977 let mut frame = heapless::Vec::new();
978 let _ = frame.extend_from_slice(&buffer[..len]);
979 self.queue_transmit(destination, None, frame, None);
980 }
981 }
982 }
983 self.coalesce
984 .record(lookup.conversation, &lookup.sequence, now_ms);
985 }
986
987 pub fn tick(&mut self, now_ms: u64) {
989 self.expire_slots(now_ms);
990 self.notify_deadlines(now_ms);
991 self.schedule_fragment_repairs(now_ms);
992 self.transmit_due_repairs(now_ms);
993 }
994
995 fn notify_deadlines(&mut self, now_ms: u64) {
1000 for index in 0..SLOTS {
1001 let due = self.pool.slots[index].as_ref().is_some_and(|slot| {
1002 slot.announced
1003 && !slot.notified
1004 && !slot.is_complete()
1005 && now_ms >= slot.created_ms + self.config.fragment_notify_ms
1006 });
1007 if !due {
1008 continue;
1009 }
1010 let body = self.render_to_arena(index, false);
1011 let slot = self.pool.slots[index].as_mut().expect("occupied");
1012 slot.notified = true;
1013 let status = CompletionStatus::Partial {
1014 present: slot.present,
1015 count: slot.count,
1016 finalized: false,
1017 };
1018 let handle = slot.handle;
1019 self.emit_mutation(
1020 handle,
1021 MutationKind::UpdateBody {
1022 body,
1023 status,
1024 late: false,
1025 notify: true,
1026 },
1027 );
1028 }
1029 }
1030
1031 fn receive_content(
1036 &mut self,
1037 envelope: &Envelope,
1038 sender_full_key: Option<PublicKey>,
1039 content: &validate::ContentMessage<'_>,
1040 now_ms: u64,
1041 ) {
1042 let key = StreamKey {
1043 conversation: envelope.conversation,
1044 sender: envelope.sender,
1045 };
1046 self.ensure_inbound(key, now_ms);
1047 let stream = self.inbound.get_mut(&key).expect("just ensured");
1048 stream.last_active_ms = now_ms;
1049
1050 if let Some(full_key) = sender_full_key {
1052 match stream.sender_key {
1053 None => stream.sender_key = Some(full_key),
1054 Some(existing) if existing != full_key => {
1055 if !stream.collided {
1056 stream.collided = true;
1057 let conversation = envelope.conversation;
1058 self.push_output(Output::Diagnostic(Diagnostic::HintCollision {
1059 conversation,
1060 }));
1061 }
1062 }
1063 Some(_) => {}
1064 }
1065 }
1066
1067 let sequence = content.sequence;
1068 if content.sequence_reset {
1069 let repeated_id = sequence.is_some_and(|sequence| {
1070 self.inbound
1071 .get(&key)
1072 .is_some_and(|stream| stream.seen.contains(sequence.message_id))
1073 });
1074 if !repeated_id {
1075 let stream = self.inbound.get_mut(&key).expect("present");
1076 let orphans: heapless::Vec<(u8, MessageHandle), 8> = stream
1081 .pending
1082 .iter()
1083 .filter_map(|pending| pending.handle.map(|h| (pending.message_id, h)))
1084 .collect();
1085 stream.reset_epoch(None);
1086 self.pool.drop_stream(&key);
1087 for (missing, handle) in orphans {
1088 self.flip_placeholder_unavailable(key, missing, handle);
1089 }
1090 }
1091 }
1092
1093 let Some(sequence) = sequence else {
1094 self.insert_content(
1096 envelope,
1097 content,
1098 None,
1099 CompletionStatus::Complete,
1100 false,
1101 true,
1102 None,
1103 now_ms,
1104 );
1105 return;
1106 };
1107 let id = sequence.message_id;
1108
1109 let stream = self.inbound.get_mut(&key).expect("present");
1112 let mut first_sighting = true;
1113 match stream.baseline {
1114 None => {
1115 stream.baseline = Some(id);
1116 stream.seen.insert(id);
1117 }
1118 Some(baseline) => match classify(baseline, id) {
1119 SerialClass::Baseline => first_sighting = false,
1120 SerialClass::Older(_) => {
1121 if stream.seen.contains(id) {
1122 first_sighting = false;
1123 } else {
1124 stream.seen.insert(id);
1125 }
1126 }
1127 SerialClass::Newer(delta) => {
1128 let gap = delta - 1;
1129 let collided = stream.collided;
1130 let epoch = stream.epoch;
1131 let can_repair = gap > 0
1132 && gap <= self.config.max_auto_repair_gap
1133 && !collided
1134 && content.sequence.is_some();
1135 if can_repair {
1136 let group =
1137 matches!(envelope.conversation, ConversationKey::ChannelGroup { .. });
1138 let base_deadline = now_ms + self.config.reorder_grace_ms;
1139 for step in 1..=gap {
1140 let missing = baseline.wrapping_add(step);
1141 let jitter = if group {
1142 self.jitter.jitter_ms(self.config.group_jitter_ms)
1143 } else {
1144 0
1145 };
1146 let placeholder = self.emit_gap_placeholder(key, missing, epoch);
1152 let stream = self.inbound.get_mut(&key).expect("present");
1153 let _ = stream.pending.push(PendingRepair {
1154 message_id: missing,
1155 fragment: None,
1156 deadline_ms: base_deadline + jitter,
1157 attempts: 0,
1158 handle: Some(placeholder),
1159 });
1160 }
1161 }
1162 let stream = self.inbound.get_mut(&key).expect("present");
1163 stream.seen.advance(baseline, delta);
1164 stream.baseline = Some(id);
1165 stream.seen.insert(id);
1166 }
1167 SerialClass::Ambiguous => {
1168 let orphans: heapless::Vec<(u8, MessageHandle), 8> = stream
1171 .pending
1172 .iter()
1173 .filter_map(|pending| pending.handle.map(|h| (pending.message_id, h)))
1174 .collect();
1175 stream.seen.clear();
1176 stream.pending.clear();
1177 stream.baseline = Some(id);
1178 stream.seen.insert(id);
1179 for (missing, handle) in orphans {
1180 self.flip_placeholder_unavailable(key, missing, handle);
1181 }
1182 }
1183 },
1184 }
1185
1186 if let Some(fragment) = sequence.fragment {
1192 let epoch = self.inbound.get(&key).expect("present").epoch;
1193 if !first_sighting && self.pool.find_slot(&key, epoch, id).is_none() {
1194 self.push_output(Output::Diagnostic(Diagnostic::DuplicateFragment {
1195 message_id: id,
1196 fragment: fragment.index,
1197 }));
1198 return;
1199 }
1200 }
1201
1202 let stream = self.inbound.get_mut(&key).expect("present");
1204 let fragment_index = sequence.fragment.map(|fragment| fragment.index);
1205 let was_repairing = stream
1206 .pending
1207 .iter()
1208 .any(|pending| pending.message_id == id && pending.attempts > 0);
1209 stream.cancel_pending(id, fragment_index);
1210 if was_repairing && sequence.fragment.is_none() {
1211 self.push_output(Output::Event(Event::RepairFinished {
1212 conversation: key.conversation,
1213 sender: key.sender,
1214 message_id: id,
1215 outcome: RepairOutcome::Repaired,
1216 }));
1217 }
1218
1219 match sequence.fragment {
1220 Some(fragment) => {
1221 self.receive_fragment(envelope, content, key, id, fragment, now_ms);
1222 }
1223 None => {
1224 if !first_sighting {
1225 self.push_output(Output::Diagnostic(Diagnostic::DuplicateMessage {
1226 message_id: id,
1227 }));
1228 return;
1229 }
1230 self.receive_single_frame(envelope, content, key, id, now_ms);
1231 }
1232 }
1233 }
1234
1235 fn receive_single_frame(
1236 &mut self,
1237 envelope: &Envelope,
1238 content: &validate::ContentMessage<'_>,
1239 key: StreamKey,
1240 id: u8,
1241 now_ms: u64,
1242 ) {
1243 let placeholder = self
1246 .inbound
1247 .get(&key)
1248 .and_then(|stream| stream.refs.lookup(id));
1249
1250 if let Some(mut original_id) = content.editing {
1251 if let Some(stream) = self.inbound.get_mut(&key) {
1252 original_id = stream.edit_refs.resolve(original_id);
1256 stream.edit_refs.record(id, original_id);
1257 }
1258 if let Some(handle) = placeholder {
1261 self.emit_mutation(
1262 handle,
1263 MutationKind::Delete {
1264 conversation: key.conversation,
1265 original: ResolvedRef::Handle(handle),
1266 },
1267 );
1268 if let Some(stream) = self.inbound.get_mut(&key) {
1269 stream.refs.retire(id);
1270 }
1271 }
1272 if self.fill_gap_with_edit(envelope, key, original_id, content.body) {
1277 return;
1278 }
1279 if let Some(stream) = self.inbound.get_mut(&key) {
1284 stream.seen.insert(original_id);
1285 }
1286 let original = self
1288 .inbound
1289 .get(&key)
1290 .and_then(|stream| stream.refs.lookup(original_id))
1291 .map(ResolvedRef::Handle)
1292 .unwrap_or(ResolvedRef::Unresolved(
1293 crate::model::WireRef::SenderScoped {
1294 sender: envelope.sender,
1295 message_id: original_id,
1296 },
1297 ));
1298 let handle = self.alloc_handle();
1299 let kind = if content.body.is_empty() {
1300 MutationKind::Delete {
1301 conversation: key.conversation,
1302 original,
1303 }
1304 } else {
1305 let body = core::str::from_utf8(content.body).unwrap_or("");
1306 let body_ref = self
1307 .arena_store(body)
1308 .unwrap_or(BodyRef { offset: 0, len: 0 });
1309 MutationKind::Edit {
1310 conversation: key.conversation,
1311 original,
1312 body: body_ref,
1313 }
1314 };
1315 self.emit_mutation(handle, kind);
1316 return;
1317 }
1318
1319 let late = placeholder.is_some();
1320 let handle = self.insert_content(
1321 envelope,
1322 content,
1323 Some(id),
1324 CompletionStatus::Complete,
1325 late,
1326 true,
1327 placeholder,
1328 now_ms,
1329 );
1330 if let Some(stream) = self.inbound.get_mut(&key) {
1331 stream.refs.record(id, handle);
1332 }
1333 }
1334
1335 fn receive_fragment(
1336 &mut self,
1337 envelope: &Envelope,
1338 content: &validate::ContentMessage<'_>,
1339 key: StreamKey,
1340 id: u8,
1341 fragment: Fragment,
1342 now_ms: u64,
1343 ) {
1344 if fragment.count > FRAGMENT_COUNT_MAX {
1345 self.push_output(Output::Diagnostic(Diagnostic::FragmentCountExceeded {
1347 message_id: id,
1348 count: fragment.count,
1349 }));
1350 return;
1351 }
1352 let epoch = self
1353 .inbound
1354 .get(&key)
1355 .map(|stream| stream.epoch)
1356 .unwrap_or(0);
1357
1358 let slot_index = match self.pool.find_slot(&key, epoch, id) {
1359 Some(index) => {
1360 let slot = self.pool.slots[index].as_ref().expect("occupied");
1361 if slot.count != fragment.count {
1362 self.push_output(Output::Diagnostic(Diagnostic::FragmentCountMismatch {
1363 message_id: id,
1364 }));
1365 return;
1366 }
1367 index
1368 }
1369 None => {
1370 let placeholder = self
1374 .inbound
1375 .get(&key)
1376 .and_then(|stream| stream.refs.lookup(id));
1377 let handle = placeholder.unwrap_or_else(|| self.alloc_handle());
1378 let mut slot = empty_slot(key, epoch, id, fragment.count, handle, now_ms);
1379 slot.late = placeholder.is_some();
1380 slot.deadline_ms = now_ms + self.config.reassembly_ttl_ms;
1381 let group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
1382 let jitter = if group {
1383 self.jitter.jitter_ms(self.config.group_jitter_ms)
1384 } else {
1385 0
1386 };
1387 slot.repair_at_ms = now_ms + self.config.fragment_grace_ms + jitter;
1388 match self.pool.open_slot(slot.clone()) {
1389 Some(index) => index,
1390 None => {
1391 if let Some(oldest) = self.pool.oldest_slot() {
1393 self.finalize_slot(oldest, now_ms, RepairOutcome::Expired, true);
1394 }
1395 match self.pool.open_slot(slot) {
1396 Some(index) => index,
1397 None => {
1398 self.push_output(Output::Diagnostic(
1399 Diagnostic::ReassemblyEvicted { message_id: id },
1400 ));
1401 return;
1402 }
1403 }
1404 }
1405 }
1406 }
1407 };
1408
1409 if fragment.index == 0 {
1414 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1415 if !slot.have_meta {
1416 slot.meta = fragment::FirstMeta {
1417 message_type_byte: content.message_type.to_byte(),
1418 regarding: content.regarding,
1419 editing: content.editing,
1420 };
1421 slot.have_meta = true;
1422 }
1423 }
1424
1425 if content.body.len() > FRAGMENT_BODY_MAX {
1426 self.push_output(Output::Diagnostic(Diagnostic::OversizedFragment {
1432 message_id: id,
1433 fragment: fragment.index,
1434 }));
1435 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1436 let bit = 1u16 << fragment.index;
1437 if slot.present & bit == 0 {
1438 slot.unavailable |= bit;
1439 }
1440 self.publish_slot(envelope, content, slot_index, now_ms);
1441 if self.pool.slots[slot_index]
1442 .as_ref()
1443 .is_some_and(|slot| slot.is_settled() && !slot.is_complete())
1444 {
1445 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1446 }
1447 return;
1448 }
1449
1450 let mut outcome = self
1452 .pool
1453 .insert_fragment(slot_index, fragment.index, content.body);
1454 if outcome == InsertOutcome::NoSpace {
1455 let oldest = self.pool.oldest_slot().filter(|index| *index != slot_index);
1457 if let Some(oldest) = oldest {
1458 self.finalize_slot(oldest, now_ms, RepairOutcome::Expired, true);
1459 outcome = self
1460 .pool
1461 .insert_fragment(slot_index, fragment.index, content.body);
1462 }
1463 }
1464 match outcome {
1465 InsertOutcome::Stored => {}
1466 InsertOutcome::Duplicate => {
1467 self.push_output(Output::Diagnostic(Diagnostic::DuplicateFragment {
1468 message_id: id,
1469 fragment: fragment.index,
1470 }));
1471 return;
1472 }
1473 InsertOutcome::Conflict => {
1474 self.push_output(Output::Diagnostic(Diagnostic::FragmentConflict {
1475 message_id: id,
1476 fragment: fragment.index,
1477 }));
1478 return;
1479 }
1480 InsertOutcome::NoSpace => {
1481 self.push_output(Output::Diagnostic(Diagnostic::ReassemblyEvicted {
1482 message_id: id,
1483 }));
1484 return;
1485 }
1486 InsertOutcome::TooLarge => {
1487 self.push_output(Output::Diagnostic(Diagnostic::OversizedFragment {
1490 message_id: id,
1491 fragment: fragment.index,
1492 }));
1493 return;
1494 }
1495 }
1496
1497 {
1504 let group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
1505 let jitter = if group {
1506 self.jitter.jitter_ms(self.config.group_jitter_ms)
1507 } else {
1508 0
1509 };
1510 let grace = self.config.fragment_grace_ms;
1511 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1512 let gap = now_ms.saturating_sub(slot.last_fragment_ms);
1513 slot.last_fragment_ms = now_ms;
1514 let holdoff = grace.max(gap.saturating_mul(2));
1515 slot.repair_at_ms = slot.repair_at_ms.max(now_ms + holdoff + jitter);
1516 }
1517
1518 self.publish_slot(envelope, content, slot_index, now_ms);
1519 if self.pool.slots[slot_index]
1523 .as_ref()
1524 .is_some_and(|slot| slot.is_settled() && !slot.is_complete())
1525 {
1526 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1527 }
1528 }
1529
1530 fn publish_slot(
1539 &mut self,
1540 envelope: &Envelope,
1541 content: &validate::ContentMessage<'_>,
1542 slot_index: usize,
1543 now_ms: u64,
1544 ) {
1545 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1546 let key = slot.stream;
1547 let handle = slot.handle;
1548 let complete = slot.is_complete();
1549 let have_meta = slot.have_meta;
1550 let is_edit = slot.meta.editing.is_some();
1551 let announced = slot.announced;
1552 let late = slot.late;
1553 let notified = slot.notified;
1554 let id = slot.message_id;
1555 let present = slot.present;
1556 let count = slot.count;
1557
1558 if !have_meta {
1559 return;
1562 }
1563
1564 if complete && is_edit {
1565 let mut original_id = self.pool.slots[slot_index]
1566 .as_ref()
1567 .expect("occupied")
1568 .meta
1569 .editing
1570 .expect("checked");
1571 if let Some(stream) = self.inbound.get_mut(&key) {
1572 original_id = stream.edit_refs.resolve(original_id);
1573 stream.edit_refs.record(id, original_id);
1574 }
1575 if let Some(stream) = self.inbound.get_mut(&key) {
1578 stream.seen.insert(original_id);
1579 }
1580 let original = self
1581 .inbound
1582 .get(&key)
1583 .and_then(|stream| stream.refs.lookup(original_id))
1584 .map(ResolvedRef::Handle)
1585 .unwrap_or(ResolvedRef::Unresolved(
1586 crate::model::WireRef::SenderScoped {
1587 sender: key.sender,
1588 message_id: original_id,
1589 },
1590 ));
1591 let body_ref = self.render_to_arena(slot_index, true);
1592 if late {
1596 self.emit_mutation(
1597 handle,
1598 MutationKind::Delete {
1599 conversation: key.conversation,
1600 original: ResolvedRef::Handle(handle),
1601 },
1602 );
1603 if let Some(stream) = self.inbound.get_mut(&key) {
1604 stream.refs.retire(id);
1605 }
1606 }
1607 {
1611 let mut scratch = [0u8; REASSEMBLED_BODY_MAX + 64];
1612 let len = (body_ref.len as usize).min(scratch.len());
1613 let start = body_ref.offset as usize;
1614 scratch[..len].copy_from_slice(&self.arena[start..start + len]);
1615 if self.fill_gap_with_edit(envelope, key, original_id, &scratch[..len]) {
1616 self.pool.close_slot(slot_index);
1617 return;
1618 }
1619 }
1620 let edit_handle = if late { self.alloc_handle() } else { handle };
1621 let kind = if body_ref.len == 0 {
1622 MutationKind::Delete {
1623 conversation: key.conversation,
1624 original,
1625 }
1626 } else {
1627 MutationKind::Edit {
1628 conversation: key.conversation,
1629 original,
1630 body: body_ref,
1631 }
1632 };
1633 self.emit_mutation(edit_handle, kind);
1634 self.pool.close_slot(slot_index);
1635 return;
1636 }
1637 if is_edit {
1638 return;
1640 }
1641
1642 let status = if complete {
1643 CompletionStatus::Complete
1644 } else {
1645 CompletionStatus::Partial {
1646 present,
1647 count,
1648 finalized: false,
1649 }
1650 };
1651 let body_ref = self.render_to_arena(slot_index, complete);
1652 let notify = complete && !notified;
1655 if notify && let Some(slot) = self.pool.slots[slot_index].as_mut() {
1656 slot.notified = true;
1657 }
1658
1659 if !announced {
1660 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1661 slot.announced = true;
1662 let meta = slot.meta;
1663 let message_type = meta.message_type();
1664 let sender_handle = content
1665 .sender_handle
1666 .and_then(|text| self.arena_store(text));
1667 let regarding = meta
1668 .regarding
1669 .map(|r| self.resolved_from_wire(key.conversation, Some(key.sender), r));
1670 self.emit_mutation(
1671 handle,
1672 MutationKind::Insert {
1673 conversation: envelope.conversation,
1674 sender: envelope.sender,
1675 direction: Direction::Inbound,
1676 message_type,
1677 wire_id: Some(id),
1678 epoch: self.inbound.get(&key).map(|s| s.epoch).unwrap_or(0),
1679 client_token: None,
1680 sender_handle,
1681 regarding,
1682 bg_color: content.bg_color,
1683 text_color: content.text_color,
1684 body: body_ref,
1685 status,
1686 presence: Presence::Present,
1687 late,
1688 notify,
1689 },
1690 );
1691 if let Some(stream) = self.inbound.get_mut(&key) {
1692 stream.refs.record(id, handle);
1693 }
1694 } else {
1695 self.emit_mutation(
1696 handle,
1697 MutationKind::UpdateBody {
1698 body: body_ref,
1699 status,
1700 late: false,
1701 notify,
1702 },
1703 );
1704 }
1705
1706 if complete {
1707 self.pool.close_slot(slot_index);
1708 let was_repairing = self.inbound.get_mut(&key).is_some_and(|stream| {
1709 let repairing = stream
1710 .pending
1711 .iter()
1712 .any(|pending| pending.message_id == id && pending.attempts > 0);
1713 stream.cancel_pending(id, None);
1714 repairing
1715 });
1716 if was_repairing {
1717 self.push_output(Output::Event(Event::RepairFinished {
1718 conversation: key.conversation,
1719 sender: key.sender,
1720 message_id: id,
1721 outcome: RepairOutcome::Repaired,
1722 }));
1723 }
1724 }
1725 let _ = now_ms;
1726 }
1727
1728 #[allow(clippy::too_many_arguments)]
1729 fn insert_content(
1730 &mut self,
1731 envelope: &Envelope,
1732 content: &validate::ContentMessage<'_>,
1733 wire_id: Option<u8>,
1734 status: CompletionStatus,
1735 late: bool,
1736 notify: bool,
1737 reuse: Option<MessageHandle>,
1738 now_ms: u64,
1739 ) -> MessageHandle {
1740 let handle = reuse.unwrap_or_else(|| self.alloc_handle());
1741 let body = core::str::from_utf8(content.body).unwrap_or("");
1742 let body_ref = self
1743 .arena_store(body)
1744 .unwrap_or(BodyRef { offset: 0, len: 0 });
1745 let sender_handle = content
1746 .sender_handle
1747 .and_then(|handle_text| self.arena_store(handle_text));
1748 let regarding = content
1749 .regarding
1750 .map(|r| self.resolved_from_wire(envelope.conversation, Some(envelope.sender), r));
1751 let epoch = self
1752 .inbound
1753 .get(&StreamKey {
1754 conversation: envelope.conversation,
1755 sender: envelope.sender,
1756 })
1757 .map(|stream| stream.epoch)
1758 .unwrap_or(0);
1759 self.emit_mutation(
1760 handle,
1761 MutationKind::Insert {
1762 conversation: envelope.conversation,
1763 sender: envelope.sender,
1764 direction: Direction::Inbound,
1765 message_type: content.message_type,
1766 wire_id,
1767 epoch,
1768 client_token: None,
1769 sender_handle,
1770 regarding,
1771 bg_color: content.bg_color,
1772 text_color: content.text_color,
1773 body: body_ref,
1774 status,
1775 presence: Presence::Present,
1776 late,
1777 notify,
1778 },
1779 );
1780 let _ = now_ms;
1781 handle
1782 }
1783
1784 fn receive_resend_request(
1789 &mut self,
1790 envelope: &Envelope,
1791 sequence: MessageSequence,
1792 channel_group: bool,
1793 now_ms: u64,
1794 ) {
1795 let SenderScope::Peer(requester) = envelope.sender else {
1797 self.push_output(Output::Diagnostic(Diagnostic::UnattributableResend));
1798 return;
1799 };
1800 let conversation = if channel_group {
1802 match envelope.conversation {
1803 ConversationKey::ChannelDirect { channel, .. } => {
1804 ConversationKey::ChannelGroup { channel }
1805 }
1806 _ => {
1807 self.push_output(Output::Diagnostic(Diagnostic::UnattributableResend));
1808 return;
1809 }
1810 }
1811 } else {
1812 envelope.conversation
1813 };
1814
1815 if self.coalesce.recently_answered(
1816 &conversation,
1817 &sequence,
1818 now_ms,
1819 self.config.coalesce_window_ms,
1820 ) {
1821 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1822 message_id: sequence.message_id,
1823 }));
1824 return;
1825 }
1826
1827 let requested_fragment = sequence.fragment.map(|fragment| fragment.index);
1837 if self.saw_transmit_report
1838 && self.in_flight.iter().any(|frame| {
1839 frame.archive.is_some_and(|archive| {
1840 archive.conversation == conversation
1841 && archive.message_id == sequence.message_id
1842 && archive.fragment == requested_fragment
1843 })
1844 })
1845 {
1846 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1847 message_id: sequence.message_id,
1848 }));
1849 return;
1850 }
1851
1852 if self
1855 .lookups
1856 .iter()
1857 .any(|lookup| lookup.conversation == conversation && lookup.sequence == sequence)
1858 {
1859 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1860 message_id: sequence.message_id,
1861 }));
1862 return;
1863 }
1864
1865 let request_id = self.next_request;
1866 self.next_request = self.next_request.wrapping_add(1);
1867 if self.lookups.is_full() {
1868 self.lookups.remove(0);
1869 }
1870 let _ = self.lookups.push(PendingLookup {
1871 request_id,
1872 conversation,
1873 requester,
1874 sequence,
1875 });
1876 self.push_output(Output::LookupOutbound {
1877 request_id,
1878 conversation,
1879 sequence,
1880 });
1881 }
1882
1883 fn receive_unavailable(&mut self, envelope: &Envelope, sequence: MessageSequence, now_ms: u64) {
1884 let key = StreamKey {
1885 conversation: envelope.conversation,
1886 sender: envelope.sender,
1887 };
1888 let id = sequence.message_id;
1889 let fragment = sequence.fragment.map(|fragment| fragment.index);
1890
1891 let placeholder = self.inbound.get(&key).and_then(|stream| {
1894 stream
1895 .pending
1896 .iter()
1897 .find(|pending| pending.message_id == id && pending.fragment.is_none())
1898 .and_then(|pending| pending.handle)
1899 });
1900
1901 if let Some(stream) = self.inbound.get_mut(&key) {
1902 stream.cancel_pending(id, fragment);
1903 stream.seen.insert(id);
1905 stream.last_active_ms = now_ms;
1906 }
1907
1908 let epoch = self
1909 .inbound
1910 .get(&key)
1911 .map(|stream| stream.epoch)
1912 .unwrap_or(0);
1913 let slot = self.pool.find_slot(&key, epoch, id);
1914 if fragment.is_none()
1915 && slot.is_none()
1916 && let Some(handle) = placeholder
1917 {
1918 self.flip_placeholder_unavailable(key, id, handle);
1919 }
1920 if let Some(slot_index) = slot {
1921 match fragment {
1922 Some(index) => {
1923 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1924 let bit = 1u16 << index;
1925 if slot.present & bit == 0 {
1926 slot.unavailable |= bit;
1927 }
1928 let settled = {
1929 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1930 slot.is_settled()
1931 };
1932 if settled {
1933 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1934 } else if self.pool.slots[slot_index]
1935 .as_ref()
1936 .is_some_and(|slot| slot.announced)
1937 {
1938 let body = self.render_to_arena(slot_index, false);
1939 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1940 let status = CompletionStatus::Partial {
1941 present: slot.present,
1942 count: slot.count,
1943 finalized: false,
1944 };
1945 let handle = slot.handle;
1946 self.emit_mutation(
1947 handle,
1948 MutationKind::UpdateBody {
1949 body,
1950 status,
1951 late: false,
1952 notify: false,
1953 },
1954 );
1955 }
1956 }
1957 None => {
1958 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1959 }
1960 }
1961 }
1962
1963 self.push_output(Output::Event(Event::MessageUnavailable {
1964 conversation: envelope.conversation,
1965 sender: envelope.sender,
1966 message_id: id,
1967 fragment,
1968 }));
1969 }
1970
1971 fn expire_slots(&mut self, now_ms: u64) {
1976 for index in 0..SLOTS {
1977 let expired = self.pool.slots[index]
1978 .as_ref()
1979 .is_some_and(|slot| now_ms >= slot.deadline_ms);
1980 if expired {
1981 self.finalize_slot(index, now_ms, RepairOutcome::Expired, true);
1982 }
1983 }
1984 }
1985
1986 fn schedule_fragment_repairs(&mut self, now_ms: u64) {
1988 for index in 0..SLOTS {
1989 let Some(slot) = self.pool.slots[index].as_ref() else {
1990 continue;
1991 };
1992 if now_ms < slot.repair_at_ms || slot.is_settled() {
1993 continue;
1994 }
1995 let key = slot.stream;
1996 let id = slot.message_id;
1997 if let Some(stream) = self.inbound.get_mut(&key) {
1998 if stream.collided {
1999 continue;
2000 }
2001 if stream
2006 .pending
2007 .iter()
2008 .any(|pending| pending.message_id == id)
2009 {
2010 continue;
2011 }
2012 let next = self.pool.slots[index]
2013 .as_ref()
2014 .and_then(|slot| slot.repairable_missing().next());
2015 if let Some(fragment) = next {
2016 let _ = stream.pending.push(PendingRepair {
2017 message_id: id,
2018 fragment: Some(fragment),
2019 deadline_ms: now_ms,
2020 attempts: 0,
2021 handle: None,
2022 });
2023 if let Some(slot) = self.pool.slots[index].as_mut() {
2024 slot.repair_at_ms = now_ms + self.config.request_retry_ms;
2025 }
2026 }
2027 }
2028 }
2029 }
2030
2031 fn transmit_due_repairs(&mut self, now_ms: u64) {
2032 let mut budget = self.config.max_requests_per_tick;
2033 let keys: heapless::Vec<StreamKey, 16> = self.inbound.keys().copied().collect();
2034 for key in keys {
2035 if budget == 0 {
2036 break;
2037 }
2038 let Some(stream) = self.inbound.get(&key) else {
2039 continue;
2040 };
2041 if stream.collided
2042 || now_ms.saturating_sub(stream.last_request_ms)
2043 < self.config.min_request_interval_ms
2044 {
2045 continue;
2046 }
2047 let Some(position) = stream
2048 .pending
2049 .iter()
2050 .position(|pending| now_ms >= pending.deadline_ms)
2051 else {
2052 continue;
2053 };
2054 let pending = stream.pending[position];
2055
2056 let destination = match key.conversation {
2058 ConversationKey::Direct { peer } => Destination::Peer(peer),
2059 ConversationKey::Room { room } => Destination::Peer(room),
2060 ConversationKey::ChannelDirect { channel, peer } => {
2061 Destination::ChannelPeer { channel, peer }
2062 }
2063 ConversationKey::ChannelGroup { channel } => match stream.sender_key {
2064 Some(peer) => Destination::ChannelPeer { channel, peer },
2065 None => {
2066 let stream = self.inbound.get_mut(&key).expect("present");
2069 stream.pending.remove(position);
2070 self.push_output(Output::Event(Event::RepairFinished {
2071 conversation: key.conversation,
2072 sender: key.sender,
2073 message_id: pending.message_id,
2074 outcome: RepairOutcome::Unaddressable,
2075 }));
2076 if pending.fragment.is_none()
2077 && let Some(handle) = pending.handle
2078 {
2079 self.flip_placeholder_unavailable(key, pending.message_id, handle);
2080 }
2081 continue;
2082 }
2083 },
2084 };
2085 let channel_group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
2086
2087 let sequence = match pending.fragment {
2089 None => MessageSequence::unfragmented(pending.message_id),
2090 Some(index) => {
2091 let epoch = stream.epoch;
2092 let count = self
2093 .pool
2094 .find_slot(&key, epoch, pending.message_id)
2095 .and_then(|slot| self.pool.slots[slot].as_ref())
2096 .map(|slot| slot.count);
2097 let Some(count) = count else {
2098 let stream = self.inbound.get_mut(&key).expect("present");
2099 stream.pending.remove(position);
2100 continue;
2101 };
2102 MessageSequence {
2103 message_id: pending.message_id,
2104 fragment: Some(Fragment { index, count }),
2105 }
2106 }
2107 };
2108
2109 let request = TextMessage {
2110 message_type: MessageType::ResendRequest,
2111 sequence: Some(sequence),
2112 channel_group_resend: channel_group,
2113 ..TextMessage::basic("")
2114 };
2115 let mut buffer = [0u8; MAX_FRAME];
2116 let Ok(len) = codec::encode(&request, &mut buffer) else {
2117 continue;
2118 };
2119 let mut frame = heapless::Vec::new();
2120 let _ = frame.extend_from_slice(&buffer[..len]);
2121 self.queue_transmit(destination, None, frame, None);
2122 budget -= 1;
2123
2124 if pending.attempts == 0 {
2125 self.push_output(Output::Event(Event::RepairStarted {
2126 conversation: key.conversation,
2127 sender: key.sender,
2128 message_id: pending.message_id,
2129 fragment: pending.fragment,
2130 }));
2131 }
2132
2133 let max_attempts = self.config.max_repair_attempts;
2134 let retry_ms = self.config.request_retry_ms;
2135 let stream = self.inbound.get_mut(&key).expect("present");
2136 stream.last_request_ms = now_ms;
2137 let entry = &mut stream.pending[position];
2138 entry.attempts += 1;
2139 if entry.attempts >= max_attempts {
2140 let message_id = entry.message_id;
2141 let fragment = entry.fragment;
2142 let placeholder = entry.handle;
2143 stream.pending.remove(position);
2144 if let Some(fragment) = fragment
2145 && let Some(slot_index) = self.pool.find_slot(&key, stream.epoch, message_id)
2146 && let Some(slot) = self.pool.slots[slot_index].as_mut()
2147 {
2148 slot.repair_exhausted |= 1u16 << fragment;
2149 }
2150 self.push_output(Output::Event(Event::RepairFinished {
2151 conversation: key.conversation,
2152 sender: key.sender,
2153 message_id,
2154 outcome: RepairOutcome::Exhausted,
2155 }));
2156 if fragment.is_none()
2159 && let Some(handle) = placeholder
2160 {
2161 self.flip_placeholder_unavailable(key, message_id, handle);
2162 }
2163 } else {
2164 entry.deadline_ms = now_ms + retry_ms;
2165 }
2166 }
2167 }
2168
2169 fn finalize_slot(
2172 &mut self,
2173 slot_index: usize,
2174 now_ms: u64,
2175 outcome: RepairOutcome,
2176 evicted: bool,
2177 ) {
2178 let Some(slot) = self.pool.slots[slot_index].as_ref() else {
2179 return;
2180 };
2181 let key = slot.stream;
2182 let id = slot.message_id;
2183 let announced = slot.announced;
2184 let handle = slot.handle;
2185 let complete = slot.is_complete();
2186
2187 if announced && !complete {
2188 let body = self.render_to_arena(slot_index, true);
2189 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
2190 let status = CompletionStatus::Partial {
2191 present: slot.present,
2192 count: slot.count,
2193 finalized: true,
2194 };
2195 self.emit_mutation(
2196 handle,
2197 MutationKind::UpdateBody {
2198 body,
2199 status,
2200 late: false,
2201 notify: false,
2202 },
2203 );
2204 }
2205 self.pool.close_slot(slot_index);
2206 if evicted {
2207 self.push_output(Output::Diagnostic(Diagnostic::ReassemblyEvicted {
2208 message_id: id,
2209 }));
2210 }
2211 if let Some(stream) = self.inbound.get_mut(&key) {
2212 stream.cancel_pending(id, None);
2213 stream.last_active_ms = now_ms;
2214 }
2215 self.push_output(Output::Event(Event::RepairFinished {
2216 conversation: key.conversation,
2217 sender: key.sender,
2218 message_id: id,
2219 outcome,
2220 }));
2221 }
2222
2223 fn encode_and_queue(
2228 &mut self,
2229 conversation: ConversationKey,
2230 handle: MessageHandle,
2231 template: &TextMessage<'_>,
2232 body: &[u8],
2233 message_id: u8,
2234 ) -> Result<(), ComposeError> {
2235 let destination = destination_for(&conversation);
2236
2237 let mut trial = *template;
2239 trial.body = &[];
2240 let mut buffer = [0u8; MAX_FRAME];
2241 let overhead = codec::encode(&trial, &mut buffer).map_err(ComposeError::Encode)?;
2242 let single_budget = MAX_FRAME.saturating_sub(overhead + 1);
2243
2244 let Some(plan) =
2245 FragmentPlan::plan(body.len(), single_budget).map_err(|_| ComposeError::TooLarge)?
2246 else {
2247 let mut message = *template;
2248 message.body = body;
2249 let len = codec::encode(&message, &mut buffer).map_err(ComposeError::Encode)?;
2250 let mut frame = heapless::Vec::new();
2251 let _ = frame.extend_from_slice(&buffer[..len]);
2252 self.queue_transmit(
2253 destination,
2254 Some(ArchiveKey {
2255 conversation,
2256 message_id,
2257 fragment: None,
2258 }),
2259 frame,
2260 Some((handle, None)),
2261 );
2262 return Ok(());
2263 };
2264
2265 for index in 0..plan.count {
2266 let range = plan.range(index);
2267 let mut message = if index == 0 {
2268 *template
2269 } else {
2270 let mut continuation = TextMessage::basic("");
2272 continuation.sequence = template.sequence;
2273 continuation
2274 };
2275 message.sequence = Some(MessageSequence {
2276 message_id,
2277 fragment: Some(Fragment {
2278 index,
2279 count: plan.count,
2280 }),
2281 });
2282 message.body = &body[range];
2283 let len = codec::encode(&message, &mut buffer).map_err(ComposeError::Encode)?;
2284 if len > MAX_FRAME {
2285 return Err(ComposeError::TooLarge);
2286 }
2287 let mut frame = heapless::Vec::new();
2288 let _ = frame.extend_from_slice(&buffer[..len]);
2289 self.queue_transmit(
2290 destination,
2291 Some(ArchiveKey {
2292 conversation,
2293 message_id,
2294 fragment: Some(index),
2295 }),
2296 frame,
2297 Some((handle, Some(index))),
2298 );
2299 }
2300 Ok(())
2301 }
2302
2303 fn archive_replacement(&mut self, conversation: ConversationKey, message_id: u8, body: &[u8]) {
2313 let mut template = TextMessage::basic("");
2314 template.sequence = Some(MessageSequence::unfragmented(message_id));
2315
2316 let mut buffer = [0u8; MAX_FRAME];
2317 let Ok(overhead) = codec::encode(&template, &mut buffer) else {
2318 return;
2319 };
2320 let single_budget = MAX_FRAME.saturating_sub(overhead + 1);
2321 let Ok(plan) = FragmentPlan::plan(body.len(), single_budget) else {
2322 return;
2323 };
2324
2325 let Some(plan) = plan else {
2326 let mut message = template;
2327 message.body = body;
2328 let Ok(len) = codec::encode(&message, &mut buffer) else {
2329 return;
2330 };
2331 let mut frame = heapless::Vec::new();
2332 let _ = frame.extend_from_slice(&buffer[..len]);
2333 self.push_output(Output::StoreArchive {
2334 key: ArchiveKey {
2335 conversation,
2336 message_id,
2337 fragment: None,
2338 },
2339 payload: frame,
2340 });
2341 return;
2342 };
2343
2344 for index in 0..plan.count {
2345 let range = plan.range(index);
2346 let mut message = TextMessage::basic("");
2347 message.sequence = Some(MessageSequence {
2348 message_id,
2349 fragment: Some(Fragment {
2350 index,
2351 count: plan.count,
2352 }),
2353 });
2354 message.body = &body[range];
2355 let Ok(len) = codec::encode(&message, &mut buffer) else {
2356 return;
2357 };
2358 let mut frame = heapless::Vec::new();
2359 let _ = frame.extend_from_slice(&buffer[..len]);
2360 self.push_output(Output::StoreArchive {
2361 key: ArchiveKey {
2362 conversation,
2363 message_id,
2364 fragment: Some(index),
2365 },
2366 payload: frame,
2367 });
2368 }
2369 }
2370
2371 fn queue_transmit(
2372 &mut self,
2373 destination: Destination,
2374 archive: Option<ArchiveKey>,
2375 payload: heapless::Vec<u8, MAX_FRAME>,
2376 track: Option<(MessageHandle, Option<u8>)>,
2377 ) -> u32 {
2378 let transmission_id = self.next_transmission;
2379 self.next_transmission = self.next_transmission.wrapping_add(1);
2380 if let Some((handle, fragment)) = track {
2381 if self.in_flight.is_full() {
2382 self.in_flight.remove(0);
2383 }
2384 let _ = self.in_flight.push(InFlightFrame {
2385 transmission_id,
2386 handle,
2387 fragment,
2388 archive,
2389 expects_ack: matches!(destination, Destination::Peer(_)),
2390 });
2391 }
2392 self.push_output(Output::Transmit(Transmission {
2393 transmission_id,
2394 destination,
2395 archive,
2396 payload,
2397 }));
2398 transmission_id
2399 }
2400
2401 fn wire_reference_for(
2407 &self,
2408 conversation: ConversationKey,
2409 handle: MessageHandle,
2410 ) -> Option<Regarding> {
2411 let multicast = conversation.uses_multicast_references();
2412 if let Some(stream) = self.outbound.get(&conversation)
2413 && let Some(id) = stream.refs.lookup_handle(handle)
2414 {
2415 return Some(if multicast {
2416 Regarding::Multicast {
2417 message_id: id,
2418 source_prefix: umsh_core::NodeHint([
2419 self.local_key.0[0],
2420 self.local_key.0[1],
2421 self.local_key.0[2],
2422 ]),
2423 }
2424 } else {
2425 Regarding::Unicast { message_id: id }
2426 });
2427 }
2428 for (key, stream) in self.inbound.iter() {
2429 if key.conversation != conversation {
2430 continue;
2431 }
2432 if let Some(id) = stream.refs.lookup_handle(handle) {
2433 return Some(if multicast {
2434 let prefix = key.sender.hint()?;
2435 Regarding::Multicast {
2436 message_id: id,
2437 source_prefix: prefix,
2438 }
2439 } else {
2440 Regarding::Unicast { message_id: id }
2441 });
2442 }
2443 }
2444 None
2445 }
2446
2447 fn resolve_regarding(
2450 &self,
2451 conversation: ConversationKey,
2452 target: RegardingRef,
2453 ) -> Result<(Regarding, ResolvedRef), ComposeError> {
2454 let (message_id, direction, sender_hint, epoch) = match target {
2455 RegardingRef::Handle(handle) => {
2456 let wire = self
2457 .wire_reference_for(conversation, handle)
2458 .ok_or(ComposeError::UnknownRegarding)?;
2459 return Ok((wire, ResolvedRef::Handle(handle)));
2460 }
2461 RegardingRef::Wire {
2462 message_id,
2463 direction,
2464 sender_hint,
2465 epoch,
2466 } => (message_id, direction, sender_hint, epoch),
2467 };
2468
2469 let sender = regarding_sender(conversation, direction, sender_hint)
2470 .ok_or(ComposeError::UnknownRegarding)?;
2471 match direction {
2472 Direction::Outbound => {
2473 let stream = self
2474 .outbound
2475 .get(&conversation)
2476 .ok_or(ComposeError::UnknownRegarding)?;
2477 if stream.announce_reset || epoch.is_some_and(|epoch| stream.epoch != epoch) {
2480 return Err(ComposeError::UnknownRegarding);
2481 }
2482 let delta = stream.next_id.wrapping_sub(message_id);
2484 if delta == 0 || delta > 128 {
2485 return Err(ComposeError::UnknownRegarding);
2486 }
2487 }
2488 Direction::Inbound => {
2489 let live = self.inbound.get(&StreamKey {
2490 conversation,
2491 sender,
2492 });
2493 if let Some(stream) = live
2494 && epoch.is_some_and(|epoch| stream.epoch != epoch)
2495 {
2496 return Err(ComposeError::UnknownRegarding);
2497 }
2498 }
2499 }
2500
2501 let wire = if conversation.uses_multicast_references() {
2502 let source_prefix = match direction {
2503 Direction::Outbound => umsh_core::NodeHint([
2504 self.local_key.0[0],
2505 self.local_key.0[1],
2506 self.local_key.0[2],
2507 ]),
2508 Direction::Inbound => sender.hint().ok_or(ComposeError::UnknownRegarding)?,
2509 };
2510 Regarding::Multicast {
2511 message_id,
2512 source_prefix,
2513 }
2514 } else {
2515 Regarding::Unicast { message_id }
2516 };
2517 Ok((
2520 wire,
2521 ResolvedRef::Unresolved(crate::model::WireRef::SenderScoped { sender, message_id }),
2522 ))
2523 }
2524
2525 fn resolved_from_wire(
2527 &self,
2528 conversation: ConversationKey,
2529 sender: Option<SenderScope>,
2530 regarding: Regarding,
2531 ) -> ResolvedRef {
2532 match regarding {
2533 Regarding::Multicast {
2534 message_id,
2535 source_prefix,
2536 } => {
2537 let local_prefix = umsh_core::NodeHint([
2538 self.local_key.0[0],
2539 self.local_key.0[1],
2540 self.local_key.0[2],
2541 ]);
2542 if source_prefix == local_prefix
2543 && let Some(stream) = self.outbound.get(&conversation)
2544 {
2545 let message_id = stream.edit_refs.resolve(message_id);
2546 if let Some(handle) = stream.refs.lookup(message_id) {
2547 return ResolvedRef::Handle(handle);
2548 }
2549 }
2550 let key = StreamKey {
2551 conversation,
2552 sender: SenderScope::ClaimedMember(source_prefix),
2553 };
2554 let message_id = self
2558 .inbound
2559 .get(&key)
2560 .map(|stream| stream.edit_refs.resolve(message_id))
2561 .unwrap_or(message_id);
2562 let unresolved = ResolvedRef::Unresolved(crate::model::WireRef::SenderScoped {
2563 sender: SenderScope::ClaimedMember(source_prefix),
2564 message_id,
2565 });
2566 match self.inbound.get(&key) {
2567 Some(stream) if !stream.collided => stream
2568 .refs
2569 .lookup(message_id)
2570 .map(ResolvedRef::Handle)
2571 .unwrap_or(unresolved),
2572 _ => unresolved,
2573 }
2574 }
2575 Regarding::Unicast { message_id } => {
2576 let inbound_key = sender.map(|sender| StreamKey {
2579 conversation,
2580 sender,
2581 });
2582 let inbound_stream = inbound_key.and_then(|key| self.inbound.get(&key));
2583 let outbound_stream = self.outbound.get(&conversation);
2584 let message_id = inbound_stream
2587 .map(|stream| stream.edit_refs.resolve(message_id))
2588 .filter(|resolved| *resolved != message_id)
2589 .or_else(|| outbound_stream.map(|stream| stream.edit_refs.resolve(message_id)))
2590 .unwrap_or(message_id);
2591 let inbound = inbound_stream.and_then(|stream| stream.refs.lookup(message_id));
2592 let outbound = outbound_stream.and_then(|stream| stream.refs.lookup(message_id));
2593 match (inbound, outbound) {
2594 (Some(handle), None) | (None, Some(handle)) => ResolvedRef::Handle(handle),
2595 _ => ResolvedRef::Unresolved(match sender {
2596 Some(sender) => crate::model::WireRef::SenderScoped { sender, message_id },
2597 None => crate::model::WireRef::RoomCanonical { message_id },
2598 }),
2599 }
2600 }
2601 }
2602 }
2603
2604 fn ensure_outbound(&mut self, conversation: ConversationKey, now_ms: u64) {
2609 if self.outbound.contains_key(&conversation) {
2610 return;
2611 }
2612 if self.outbound.len() == self.outbound.capacity()
2613 && let Some(oldest) = self
2614 .outbound
2615 .iter()
2616 .min_by_key(|(_, stream)| stream.last_active_ms)
2617 .map(|(key, _)| *key)
2618 {
2619 if let Some(stream) = self.outbound.remove(&oldest)
2622 && !stream.announce_reset
2623 {
2624 self.stash_checkpoint(oldest, stream.next_id, stream.epoch);
2625 }
2626 self.push_output(Output::Diagnostic(Diagnostic::StreamEvicted));
2627 }
2628 let cold = self
2629 .cold_checkpoints
2630 .iter()
2631 .position(|(key, _, _)| *key == conversation);
2632 let stream = match cold {
2633 Some(position) => {
2634 let (_, next_id, epoch) = self.cold_checkpoints.remove(position);
2635 OutboundStream {
2636 next_id,
2637 epoch,
2638 announce_reset: false,
2639 refs: Default::default(),
2640 edit_refs: Default::default(),
2641 last_active_ms: now_ms,
2642 }
2643 }
2644 None => OutboundStream::fresh(now_ms),
2645 };
2646 let _ = self.outbound.insert(conversation, stream);
2647 }
2648
2649 fn stash_checkpoint(&mut self, conversation: ConversationKey, next_id: u8, epoch: u16) {
2652 self.cold_checkpoints
2653 .retain(|(key, _, _)| *key != conversation);
2654 if self.cold_checkpoints.is_full() {
2655 self.cold_checkpoints.remove(0);
2656 }
2657 let _ = self.cold_checkpoints.push((conversation, next_id, epoch));
2658 }
2659
2660 fn ensure_inbound(&mut self, key: StreamKey, now_ms: u64) {
2661 if self.inbound.contains_key(&key) {
2662 return;
2663 }
2664 if self.inbound.len() == self.inbound.capacity()
2665 && let Some(oldest) = self
2666 .inbound
2667 .iter()
2668 .min_by_key(|(_, stream)| stream.last_active_ms)
2669 .map(|(key, _)| *key)
2670 {
2671 self.pool.drop_stream(&oldest);
2672 self.inbound.remove(&oldest);
2673 self.push_output(Output::Diagnostic(Diagnostic::StreamEvicted));
2674 }
2675 let _ = self.inbound.insert(key, InboundStream::new(now_ms));
2676 }
2677
2678 fn alloc_handle(&mut self) -> MessageHandle {
2679 let handle = MessageHandle(self.next_handle);
2680 self.next_handle = self.next_handle.wrapping_add(1);
2681 handle
2682 }
2683
2684 fn emit_gap_placeholder(&mut self, key: StreamKey, id: u8, epoch: u16) -> MessageHandle {
2688 let handle = self.alloc_handle();
2689 self.emit_mutation(
2690 handle,
2691 MutationKind::Insert {
2692 conversation: key.conversation,
2693 sender: key.sender,
2694 direction: Direction::Inbound,
2695 message_type: MessageType::Basic,
2696 wire_id: Some(id),
2697 epoch,
2698 client_token: None,
2699 sender_handle: None,
2700 regarding: None,
2701 bg_color: None,
2702 text_color: None,
2703 body: BodyRef { offset: 0, len: 0 },
2704 status: CompletionStatus::Complete,
2705 presence: Presence::GapPending,
2706 late: false,
2707 notify: false,
2708 },
2709 );
2710 if let Some(stream) = self.inbound.get_mut(&key) {
2711 stream.refs.record(id, handle);
2712 }
2713 handle
2714 }
2715
2716 fn fill_gap_with_edit(
2723 &mut self,
2724 envelope: &Envelope,
2725 key: StreamKey,
2726 original_id: u8,
2727 body: &[u8],
2728 ) -> bool {
2729 let Some(placeholder) = self.inbound.get(&key).and_then(|stream| {
2730 stream
2731 .pending
2732 .iter()
2733 .find(|pending| pending.message_id == original_id && pending.fragment.is_none())
2734 .and_then(|pending| pending.handle)
2735 }) else {
2736 return false;
2737 };
2738
2739 if let Some(stream) = self.inbound.get_mut(&key) {
2740 stream.cancel_pending(original_id, None);
2741 stream.seen.insert(original_id);
2742 }
2743 self.push_output(Output::Event(Event::RepairFinished {
2744 conversation: key.conversation,
2745 sender: key.sender,
2746 message_id: original_id,
2747 outcome: RepairOutcome::Repaired,
2748 }));
2749
2750 if body.is_empty() {
2751 self.emit_mutation(
2753 placeholder,
2754 MutationKind::Delete {
2755 conversation: key.conversation,
2756 original: ResolvedRef::Handle(placeholder),
2757 },
2758 );
2759 return true;
2760 }
2761
2762 let text = core::str::from_utf8(body).unwrap_or("");
2763 let body_ref = self
2764 .arena_store(text)
2765 .unwrap_or(BodyRef { offset: 0, len: 0 });
2766 let epoch = self
2767 .inbound
2768 .get(&key)
2769 .map(|stream| stream.epoch)
2770 .unwrap_or(0);
2771 self.emit_mutation(
2772 placeholder,
2773 MutationKind::Insert {
2774 conversation: envelope.conversation,
2775 sender: envelope.sender,
2776 direction: Direction::Inbound,
2777 message_type: MessageType::Basic,
2778 wire_id: Some(original_id),
2779 epoch,
2780 client_token: None,
2781 sender_handle: None,
2782 regarding: None,
2783 bg_color: None,
2784 text_color: None,
2785 body: body_ref,
2786 status: CompletionStatus::Complete,
2787 presence: Presence::Present,
2788 late: true,
2789 notify: true,
2790 },
2791 );
2792 self.emit_mutation(
2794 placeholder,
2795 MutationKind::Edit {
2796 conversation: key.conversation,
2797 original: ResolvedRef::Handle(placeholder),
2798 body: body_ref,
2799 },
2800 );
2801 true
2802 }
2803
2804 fn flip_placeholder_unavailable(&mut self, key: StreamKey, id: u8, handle: MessageHandle) {
2808 let epoch = self
2809 .inbound
2810 .get(&key)
2811 .map(|stream| stream.epoch)
2812 .unwrap_or(0);
2813 self.emit_mutation(
2814 handle,
2815 MutationKind::Insert {
2816 conversation: key.conversation,
2817 sender: key.sender,
2818 direction: Direction::Inbound,
2819 message_type: MessageType::Basic,
2820 wire_id: Some(id),
2821 epoch,
2822 client_token: None,
2823 sender_handle: None,
2824 regarding: None,
2825 bg_color: None,
2826 text_color: None,
2827 body: BodyRef { offset: 0, len: 0 },
2828 status: CompletionStatus::Complete,
2829 presence: Presence::Unavailable,
2830 late: false,
2831 notify: false,
2832 },
2833 );
2834 if let Some(stream) = self.inbound.get_mut(&key) {
2835 stream.refs.retire(id);
2836 }
2837 }
2838
2839 fn emit_mutation(&mut self, handle: MessageHandle, kind: MutationKind) {
2840 let revision = self.revision;
2841 self.revision = self.revision.wrapping_add(1);
2842 self.push_output(Output::StoreMessage(MessageMutation {
2843 handle,
2844 revision,
2845 kind,
2846 }));
2847 }
2848
2849 fn push_output(&mut self, output: Output) {
2850 if self.outputs.push_back(output).is_err() {
2851 self.lost_outputs = self.lost_outputs.wrapping_add(1);
2852 }
2853 }
2854
2855 fn arena_store(&mut self, text: &str) -> Option<BodyRef> {
2856 let bytes = text.as_bytes();
2857 if self.arena_used + bytes.len() > ARENA_SIZE || bytes.len() > u16::MAX as usize {
2858 self.push_output(Output::Diagnostic(Diagnostic::OutputOverflow));
2859 return None;
2860 }
2861 let offset = self.arena_used;
2862 self.arena[offset..offset + bytes.len()].copy_from_slice(bytes);
2863 self.arena_used += bytes.len();
2864 Some(BodyRef {
2865 offset: offset as u16,
2866 len: bytes.len() as u16,
2867 })
2868 }
2869
2870 fn render_to_arena(&mut self, slot_index: usize, final_render: bool) -> BodyRef {
2872 let mut scratch = [0u8; REASSEMBLED_BODY_MAX + 64];
2873 let result = fragment::render_slot(
2874 &self.pool,
2875 slot_index,
2876 &self.config.sentinels,
2877 final_render,
2878 &mut scratch,
2879 );
2880 if result.complete && result.had_invalid {
2881 let message_id = self.pool.slots[slot_index]
2884 .as_ref()
2885 .expect("occupied")
2886 .message_id;
2887 self.push_output(Output::Diagnostic(Diagnostic::ReassembledInvalidUtf8 {
2888 message_id,
2889 }));
2890 }
2891 let text = core::str::from_utf8(&scratch[..result.len]).unwrap_or("");
2892 self.arena_store(text)
2893 .unwrap_or(BodyRef { offset: 0, len: 0 })
2894 }
2895}
2896
2897pub fn destination_for(conversation: &ConversationKey) -> Destination {
2901 match conversation {
2902 ConversationKey::Direct { peer } => Destination::Peer(*peer),
2903 ConversationKey::Room { room } => Destination::Peer(*room),
2904 ConversationKey::ChannelGroup { channel } => Destination::Channel(*channel),
2905 ConversationKey::ChannelDirect { channel, peer } => Destination::ChannelPeer {
2906 channel: *channel,
2907 peer: *peer,
2908 },
2909 }
2910}