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 let original = self
1281 .inbound
1282 .get(&key)
1283 .and_then(|stream| stream.refs.lookup(original_id))
1284 .map(ResolvedRef::Handle)
1285 .unwrap_or(ResolvedRef::Unresolved(
1286 crate::model::WireRef::SenderScoped {
1287 sender: envelope.sender,
1288 message_id: original_id,
1289 },
1290 ));
1291 let handle = self.alloc_handle();
1292 let kind = if content.body.is_empty() {
1293 MutationKind::Delete {
1294 conversation: key.conversation,
1295 original,
1296 }
1297 } else {
1298 let body = core::str::from_utf8(content.body).unwrap_or("");
1299 let body_ref = self
1300 .arena_store(body)
1301 .unwrap_or(BodyRef { offset: 0, len: 0 });
1302 MutationKind::Edit {
1303 conversation: key.conversation,
1304 original,
1305 body: body_ref,
1306 }
1307 };
1308 self.emit_mutation(handle, kind);
1309 return;
1310 }
1311
1312 let late = placeholder.is_some();
1313 let handle = self.insert_content(
1314 envelope,
1315 content,
1316 Some(id),
1317 CompletionStatus::Complete,
1318 late,
1319 true,
1320 placeholder,
1321 now_ms,
1322 );
1323 if let Some(stream) = self.inbound.get_mut(&key) {
1324 stream.refs.record(id, handle);
1325 }
1326 }
1327
1328 fn receive_fragment(
1329 &mut self,
1330 envelope: &Envelope,
1331 content: &validate::ContentMessage<'_>,
1332 key: StreamKey,
1333 id: u8,
1334 fragment: Fragment,
1335 now_ms: u64,
1336 ) {
1337 if fragment.count > FRAGMENT_COUNT_MAX {
1338 self.push_output(Output::Diagnostic(Diagnostic::FragmentCountExceeded {
1340 message_id: id,
1341 count: fragment.count,
1342 }));
1343 return;
1344 }
1345 let epoch = self
1346 .inbound
1347 .get(&key)
1348 .map(|stream| stream.epoch)
1349 .unwrap_or(0);
1350
1351 let slot_index = match self.pool.find_slot(&key, epoch, id) {
1352 Some(index) => {
1353 let slot = self.pool.slots[index].as_ref().expect("occupied");
1354 if slot.count != fragment.count {
1355 self.push_output(Output::Diagnostic(Diagnostic::FragmentCountMismatch {
1356 message_id: id,
1357 }));
1358 return;
1359 }
1360 index
1361 }
1362 None => {
1363 let placeholder = self
1367 .inbound
1368 .get(&key)
1369 .and_then(|stream| stream.refs.lookup(id));
1370 let handle = placeholder.unwrap_or_else(|| self.alloc_handle());
1371 let mut slot = empty_slot(key, epoch, id, fragment.count, handle, now_ms);
1372 slot.late = placeholder.is_some();
1373 slot.deadline_ms = now_ms + self.config.reassembly_ttl_ms;
1374 let group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
1375 let jitter = if group {
1376 self.jitter.jitter_ms(self.config.group_jitter_ms)
1377 } else {
1378 0
1379 };
1380 slot.repair_at_ms = now_ms + self.config.fragment_grace_ms + jitter;
1381 match self.pool.open_slot(slot.clone()) {
1382 Some(index) => index,
1383 None => {
1384 if let Some(oldest) = self.pool.oldest_slot() {
1386 self.finalize_slot(oldest, now_ms, RepairOutcome::Expired, true);
1387 }
1388 match self.pool.open_slot(slot) {
1389 Some(index) => index,
1390 None => {
1391 self.push_output(Output::Diagnostic(
1392 Diagnostic::ReassemblyEvicted { message_id: id },
1393 ));
1394 return;
1395 }
1396 }
1397 }
1398 }
1399 }
1400 };
1401
1402 if fragment.index == 0 {
1407 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1408 if !slot.have_meta {
1409 slot.meta = fragment::FirstMeta {
1410 message_type_byte: content.message_type.to_byte(),
1411 regarding: content.regarding,
1412 editing: content.editing,
1413 };
1414 slot.have_meta = true;
1415 }
1416 }
1417
1418 if content.body.len() > FRAGMENT_BODY_MAX {
1419 self.push_output(Output::Diagnostic(Diagnostic::OversizedFragment {
1425 message_id: id,
1426 fragment: fragment.index,
1427 }));
1428 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1429 let bit = 1u16 << fragment.index;
1430 if slot.present & bit == 0 {
1431 slot.unavailable |= bit;
1432 }
1433 self.publish_slot(envelope, content, slot_index, now_ms);
1434 if self.pool.slots[slot_index]
1435 .as_ref()
1436 .is_some_and(|slot| slot.is_settled() && !slot.is_complete())
1437 {
1438 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1439 }
1440 return;
1441 }
1442
1443 let mut outcome = self
1445 .pool
1446 .insert_fragment(slot_index, fragment.index, content.body);
1447 if outcome == InsertOutcome::NoSpace {
1448 let oldest = self.pool.oldest_slot().filter(|index| *index != slot_index);
1450 if let Some(oldest) = oldest {
1451 self.finalize_slot(oldest, now_ms, RepairOutcome::Expired, true);
1452 outcome = self
1453 .pool
1454 .insert_fragment(slot_index, fragment.index, content.body);
1455 }
1456 }
1457 match outcome {
1458 InsertOutcome::Stored => {}
1459 InsertOutcome::Duplicate => {
1460 self.push_output(Output::Diagnostic(Diagnostic::DuplicateFragment {
1461 message_id: id,
1462 fragment: fragment.index,
1463 }));
1464 return;
1465 }
1466 InsertOutcome::Conflict => {
1467 self.push_output(Output::Diagnostic(Diagnostic::FragmentConflict {
1468 message_id: id,
1469 fragment: fragment.index,
1470 }));
1471 return;
1472 }
1473 InsertOutcome::NoSpace => {
1474 self.push_output(Output::Diagnostic(Diagnostic::ReassemblyEvicted {
1475 message_id: id,
1476 }));
1477 return;
1478 }
1479 InsertOutcome::TooLarge => {
1480 self.push_output(Output::Diagnostic(Diagnostic::OversizedFragment {
1483 message_id: id,
1484 fragment: fragment.index,
1485 }));
1486 return;
1487 }
1488 }
1489
1490 {
1497 let group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
1498 let jitter = if group {
1499 self.jitter.jitter_ms(self.config.group_jitter_ms)
1500 } else {
1501 0
1502 };
1503 let grace = self.config.fragment_grace_ms;
1504 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1505 let gap = now_ms.saturating_sub(slot.last_fragment_ms);
1506 slot.last_fragment_ms = now_ms;
1507 let holdoff = grace.max(gap.saturating_mul(2));
1508 slot.repair_at_ms = slot.repair_at_ms.max(now_ms + holdoff + jitter);
1509 }
1510
1511 self.publish_slot(envelope, content, slot_index, now_ms);
1512 if self.pool.slots[slot_index]
1516 .as_ref()
1517 .is_some_and(|slot| slot.is_settled() && !slot.is_complete())
1518 {
1519 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1520 }
1521 }
1522
1523 fn publish_slot(
1532 &mut self,
1533 envelope: &Envelope,
1534 content: &validate::ContentMessage<'_>,
1535 slot_index: usize,
1536 now_ms: u64,
1537 ) {
1538 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1539 let key = slot.stream;
1540 let handle = slot.handle;
1541 let complete = slot.is_complete();
1542 let have_meta = slot.have_meta;
1543 let is_edit = slot.meta.editing.is_some();
1544 let announced = slot.announced;
1545 let late = slot.late;
1546 let notified = slot.notified;
1547 let id = slot.message_id;
1548 let present = slot.present;
1549 let count = slot.count;
1550
1551 if !have_meta {
1552 return;
1555 }
1556
1557 if complete && is_edit {
1558 let mut original_id = self.pool.slots[slot_index]
1559 .as_ref()
1560 .expect("occupied")
1561 .meta
1562 .editing
1563 .expect("checked");
1564 if let Some(stream) = self.inbound.get_mut(&key) {
1565 original_id = stream.edit_refs.resolve(original_id);
1566 stream.edit_refs.record(id, original_id);
1567 }
1568 let original = self
1569 .inbound
1570 .get(&key)
1571 .and_then(|stream| stream.refs.lookup(original_id))
1572 .map(ResolvedRef::Handle)
1573 .unwrap_or(ResolvedRef::Unresolved(
1574 crate::model::WireRef::SenderScoped {
1575 sender: key.sender,
1576 message_id: original_id,
1577 },
1578 ));
1579 let body_ref = self.render_to_arena(slot_index, true);
1580 if late {
1584 self.emit_mutation(
1585 handle,
1586 MutationKind::Delete {
1587 conversation: key.conversation,
1588 original: ResolvedRef::Handle(handle),
1589 },
1590 );
1591 if let Some(stream) = self.inbound.get_mut(&key) {
1592 stream.refs.retire(id);
1593 }
1594 }
1595 {
1599 let mut scratch = [0u8; REASSEMBLED_BODY_MAX + 64];
1600 let len = (body_ref.len as usize).min(scratch.len());
1601 let start = body_ref.offset as usize;
1602 scratch[..len].copy_from_slice(&self.arena[start..start + len]);
1603 if self.fill_gap_with_edit(envelope, key, original_id, &scratch[..len]) {
1604 self.pool.close_slot(slot_index);
1605 return;
1606 }
1607 }
1608 let edit_handle = if late { self.alloc_handle() } else { handle };
1609 let kind = if body_ref.len == 0 {
1610 MutationKind::Delete {
1611 conversation: key.conversation,
1612 original,
1613 }
1614 } else {
1615 MutationKind::Edit {
1616 conversation: key.conversation,
1617 original,
1618 body: body_ref,
1619 }
1620 };
1621 self.emit_mutation(edit_handle, kind);
1622 self.pool.close_slot(slot_index);
1623 return;
1624 }
1625 if is_edit {
1626 return;
1628 }
1629
1630 let status = if complete {
1631 CompletionStatus::Complete
1632 } else {
1633 CompletionStatus::Partial {
1634 present,
1635 count,
1636 finalized: false,
1637 }
1638 };
1639 let body_ref = self.render_to_arena(slot_index, complete);
1640 let notify = complete && !notified;
1643 if notify && let Some(slot) = self.pool.slots[slot_index].as_mut() {
1644 slot.notified = true;
1645 }
1646
1647 if !announced {
1648 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1649 slot.announced = true;
1650 let meta = slot.meta;
1651 let message_type = meta.message_type();
1652 let sender_handle = content
1653 .sender_handle
1654 .and_then(|text| self.arena_store(text));
1655 let regarding = meta
1656 .regarding
1657 .map(|r| self.resolved_from_wire(key.conversation, Some(key.sender), r));
1658 self.emit_mutation(
1659 handle,
1660 MutationKind::Insert {
1661 conversation: envelope.conversation,
1662 sender: envelope.sender,
1663 direction: Direction::Inbound,
1664 message_type,
1665 wire_id: Some(id),
1666 epoch: self.inbound.get(&key).map(|s| s.epoch).unwrap_or(0),
1667 client_token: None,
1668 sender_handle,
1669 regarding,
1670 bg_color: content.bg_color,
1671 text_color: content.text_color,
1672 body: body_ref,
1673 status,
1674 presence: Presence::Present,
1675 late,
1676 notify,
1677 },
1678 );
1679 if let Some(stream) = self.inbound.get_mut(&key) {
1680 stream.refs.record(id, handle);
1681 }
1682 } else {
1683 self.emit_mutation(
1684 handle,
1685 MutationKind::UpdateBody {
1686 body: body_ref,
1687 status,
1688 late: false,
1689 notify,
1690 },
1691 );
1692 }
1693
1694 if complete {
1695 self.pool.close_slot(slot_index);
1696 let was_repairing = self.inbound.get_mut(&key).is_some_and(|stream| {
1697 let repairing = stream
1698 .pending
1699 .iter()
1700 .any(|pending| pending.message_id == id && pending.attempts > 0);
1701 stream.cancel_pending(id, None);
1702 repairing
1703 });
1704 if was_repairing {
1705 self.push_output(Output::Event(Event::RepairFinished {
1706 conversation: key.conversation,
1707 sender: key.sender,
1708 message_id: id,
1709 outcome: RepairOutcome::Repaired,
1710 }));
1711 }
1712 }
1713 let _ = now_ms;
1714 }
1715
1716 #[allow(clippy::too_many_arguments)]
1717 fn insert_content(
1718 &mut self,
1719 envelope: &Envelope,
1720 content: &validate::ContentMessage<'_>,
1721 wire_id: Option<u8>,
1722 status: CompletionStatus,
1723 late: bool,
1724 notify: bool,
1725 reuse: Option<MessageHandle>,
1726 now_ms: u64,
1727 ) -> MessageHandle {
1728 let handle = reuse.unwrap_or_else(|| self.alloc_handle());
1729 let body = core::str::from_utf8(content.body).unwrap_or("");
1730 let body_ref = self
1731 .arena_store(body)
1732 .unwrap_or(BodyRef { offset: 0, len: 0 });
1733 let sender_handle = content
1734 .sender_handle
1735 .and_then(|handle_text| self.arena_store(handle_text));
1736 let regarding = content
1737 .regarding
1738 .map(|r| self.resolved_from_wire(envelope.conversation, Some(envelope.sender), r));
1739 let epoch = self
1740 .inbound
1741 .get(&StreamKey {
1742 conversation: envelope.conversation,
1743 sender: envelope.sender,
1744 })
1745 .map(|stream| stream.epoch)
1746 .unwrap_or(0);
1747 self.emit_mutation(
1748 handle,
1749 MutationKind::Insert {
1750 conversation: envelope.conversation,
1751 sender: envelope.sender,
1752 direction: Direction::Inbound,
1753 message_type: content.message_type,
1754 wire_id,
1755 epoch,
1756 client_token: None,
1757 sender_handle,
1758 regarding,
1759 bg_color: content.bg_color,
1760 text_color: content.text_color,
1761 body: body_ref,
1762 status,
1763 presence: Presence::Present,
1764 late,
1765 notify,
1766 },
1767 );
1768 let _ = now_ms;
1769 handle
1770 }
1771
1772 fn receive_resend_request(
1777 &mut self,
1778 envelope: &Envelope,
1779 sequence: MessageSequence,
1780 channel_group: bool,
1781 now_ms: u64,
1782 ) {
1783 let SenderScope::Peer(requester) = envelope.sender else {
1785 self.push_output(Output::Diagnostic(Diagnostic::UnattributableResend));
1786 return;
1787 };
1788 let conversation = if channel_group {
1790 match envelope.conversation {
1791 ConversationKey::ChannelDirect { channel, .. } => {
1792 ConversationKey::ChannelGroup { channel }
1793 }
1794 _ => {
1795 self.push_output(Output::Diagnostic(Diagnostic::UnattributableResend));
1796 return;
1797 }
1798 }
1799 } else {
1800 envelope.conversation
1801 };
1802
1803 if self.coalesce.recently_answered(
1804 &conversation,
1805 &sequence,
1806 now_ms,
1807 self.config.coalesce_window_ms,
1808 ) {
1809 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1810 message_id: sequence.message_id,
1811 }));
1812 return;
1813 }
1814
1815 let requested_fragment = sequence.fragment.map(|fragment| fragment.index);
1825 if self.saw_transmit_report
1826 && self.in_flight.iter().any(|frame| {
1827 frame.archive.is_some_and(|archive| {
1828 archive.conversation == conversation
1829 && archive.message_id == sequence.message_id
1830 && archive.fragment == requested_fragment
1831 })
1832 })
1833 {
1834 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1835 message_id: sequence.message_id,
1836 }));
1837 return;
1838 }
1839
1840 if self
1843 .lookups
1844 .iter()
1845 .any(|lookup| lookup.conversation == conversation && lookup.sequence == sequence)
1846 {
1847 self.push_output(Output::Diagnostic(Diagnostic::CoalescedResend {
1848 message_id: sequence.message_id,
1849 }));
1850 return;
1851 }
1852
1853 let request_id = self.next_request;
1854 self.next_request = self.next_request.wrapping_add(1);
1855 if self.lookups.is_full() {
1856 self.lookups.remove(0);
1857 }
1858 let _ = self.lookups.push(PendingLookup {
1859 request_id,
1860 conversation,
1861 requester,
1862 sequence,
1863 });
1864 self.push_output(Output::LookupOutbound {
1865 request_id,
1866 conversation,
1867 sequence,
1868 });
1869 }
1870
1871 fn receive_unavailable(&mut self, envelope: &Envelope, sequence: MessageSequence, now_ms: u64) {
1872 let key = StreamKey {
1873 conversation: envelope.conversation,
1874 sender: envelope.sender,
1875 };
1876 let id = sequence.message_id;
1877 let fragment = sequence.fragment.map(|fragment| fragment.index);
1878
1879 let placeholder = self.inbound.get(&key).and_then(|stream| {
1882 stream
1883 .pending
1884 .iter()
1885 .find(|pending| pending.message_id == id && pending.fragment.is_none())
1886 .and_then(|pending| pending.handle)
1887 });
1888
1889 if let Some(stream) = self.inbound.get_mut(&key) {
1890 stream.cancel_pending(id, fragment);
1891 stream.seen.insert(id);
1893 stream.last_active_ms = now_ms;
1894 }
1895
1896 let epoch = self
1897 .inbound
1898 .get(&key)
1899 .map(|stream| stream.epoch)
1900 .unwrap_or(0);
1901 let slot = self.pool.find_slot(&key, epoch, id);
1902 if fragment.is_none()
1903 && slot.is_none()
1904 && let Some(handle) = placeholder
1905 {
1906 self.flip_placeholder_unavailable(key, id, handle);
1907 }
1908 if let Some(slot_index) = slot {
1909 match fragment {
1910 Some(index) => {
1911 let slot = self.pool.slots[slot_index].as_mut().expect("occupied");
1912 let bit = 1u16 << index;
1913 if slot.present & bit == 0 {
1914 slot.unavailable |= bit;
1915 }
1916 let settled = {
1917 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1918 slot.is_settled()
1919 };
1920 if settled {
1921 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1922 } else if self.pool.slots[slot_index]
1923 .as_ref()
1924 .is_some_and(|slot| slot.announced)
1925 {
1926 let body = self.render_to_arena(slot_index, false);
1927 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
1928 let status = CompletionStatus::Partial {
1929 present: slot.present,
1930 count: slot.count,
1931 finalized: false,
1932 };
1933 let handle = slot.handle;
1934 self.emit_mutation(
1935 handle,
1936 MutationKind::UpdateBody {
1937 body,
1938 status,
1939 late: false,
1940 notify: false,
1941 },
1942 );
1943 }
1944 }
1945 None => {
1946 self.finalize_slot(slot_index, now_ms, RepairOutcome::Unavailable, false);
1947 }
1948 }
1949 }
1950
1951 self.push_output(Output::Event(Event::MessageUnavailable {
1952 conversation: envelope.conversation,
1953 sender: envelope.sender,
1954 message_id: id,
1955 fragment,
1956 }));
1957 }
1958
1959 fn expire_slots(&mut self, now_ms: u64) {
1964 for index in 0..SLOTS {
1965 let expired = self.pool.slots[index]
1966 .as_ref()
1967 .is_some_and(|slot| now_ms >= slot.deadline_ms);
1968 if expired {
1969 self.finalize_slot(index, now_ms, RepairOutcome::Expired, true);
1970 }
1971 }
1972 }
1973
1974 fn schedule_fragment_repairs(&mut self, now_ms: u64) {
1976 for index in 0..SLOTS {
1977 let Some(slot) = self.pool.slots[index].as_ref() else {
1978 continue;
1979 };
1980 if now_ms < slot.repair_at_ms || slot.is_settled() {
1981 continue;
1982 }
1983 let key = slot.stream;
1984 let id = slot.message_id;
1985 if let Some(stream) = self.inbound.get_mut(&key) {
1986 if stream.collided {
1987 continue;
1988 }
1989 if stream
1994 .pending
1995 .iter()
1996 .any(|pending| pending.message_id == id)
1997 {
1998 continue;
1999 }
2000 let next = self.pool.slots[index]
2001 .as_ref()
2002 .and_then(|slot| slot.repairable_missing().next());
2003 if let Some(fragment) = next {
2004 let _ = stream.pending.push(PendingRepair {
2005 message_id: id,
2006 fragment: Some(fragment),
2007 deadline_ms: now_ms,
2008 attempts: 0,
2009 handle: None,
2010 });
2011 if let Some(slot) = self.pool.slots[index].as_mut() {
2012 slot.repair_at_ms = now_ms + self.config.request_retry_ms;
2013 }
2014 }
2015 }
2016 }
2017 }
2018
2019 fn transmit_due_repairs(&mut self, now_ms: u64) {
2020 let mut budget = self.config.max_requests_per_tick;
2021 let keys: heapless::Vec<StreamKey, 16> = self.inbound.keys().copied().collect();
2022 for key in keys {
2023 if budget == 0 {
2024 break;
2025 }
2026 let Some(stream) = self.inbound.get(&key) else {
2027 continue;
2028 };
2029 if stream.collided
2030 || now_ms.saturating_sub(stream.last_request_ms)
2031 < self.config.min_request_interval_ms
2032 {
2033 continue;
2034 }
2035 let Some(position) = stream
2036 .pending
2037 .iter()
2038 .position(|pending| now_ms >= pending.deadline_ms)
2039 else {
2040 continue;
2041 };
2042 let pending = stream.pending[position];
2043
2044 let destination = match key.conversation {
2046 ConversationKey::Direct { peer } => Destination::Peer(peer),
2047 ConversationKey::Room { room } => Destination::Peer(room),
2048 ConversationKey::ChannelDirect { channel, peer } => {
2049 Destination::ChannelPeer { channel, peer }
2050 }
2051 ConversationKey::ChannelGroup { channel } => match stream.sender_key {
2052 Some(peer) => Destination::ChannelPeer { channel, peer },
2053 None => {
2054 let stream = self.inbound.get_mut(&key).expect("present");
2057 stream.pending.remove(position);
2058 self.push_output(Output::Event(Event::RepairFinished {
2059 conversation: key.conversation,
2060 sender: key.sender,
2061 message_id: pending.message_id,
2062 outcome: RepairOutcome::Unaddressable,
2063 }));
2064 if pending.fragment.is_none()
2065 && let Some(handle) = pending.handle
2066 {
2067 self.flip_placeholder_unavailable(key, pending.message_id, handle);
2068 }
2069 continue;
2070 }
2071 },
2072 };
2073 let channel_group = matches!(key.conversation, ConversationKey::ChannelGroup { .. });
2074
2075 let sequence = match pending.fragment {
2077 None => MessageSequence::unfragmented(pending.message_id),
2078 Some(index) => {
2079 let epoch = stream.epoch;
2080 let count = self
2081 .pool
2082 .find_slot(&key, epoch, pending.message_id)
2083 .and_then(|slot| self.pool.slots[slot].as_ref())
2084 .map(|slot| slot.count);
2085 let Some(count) = count else {
2086 let stream = self.inbound.get_mut(&key).expect("present");
2087 stream.pending.remove(position);
2088 continue;
2089 };
2090 MessageSequence {
2091 message_id: pending.message_id,
2092 fragment: Some(Fragment { index, count }),
2093 }
2094 }
2095 };
2096
2097 let request = TextMessage {
2098 message_type: MessageType::ResendRequest,
2099 sequence: Some(sequence),
2100 channel_group_resend: channel_group,
2101 ..TextMessage::basic("")
2102 };
2103 let mut buffer = [0u8; MAX_FRAME];
2104 let Ok(len) = codec::encode(&request, &mut buffer) else {
2105 continue;
2106 };
2107 let mut frame = heapless::Vec::new();
2108 let _ = frame.extend_from_slice(&buffer[..len]);
2109 self.queue_transmit(destination, None, frame, None);
2110 budget -= 1;
2111
2112 if pending.attempts == 0 {
2113 self.push_output(Output::Event(Event::RepairStarted {
2114 conversation: key.conversation,
2115 sender: key.sender,
2116 message_id: pending.message_id,
2117 fragment: pending.fragment,
2118 }));
2119 }
2120
2121 let max_attempts = self.config.max_repair_attempts;
2122 let retry_ms = self.config.request_retry_ms;
2123 let stream = self.inbound.get_mut(&key).expect("present");
2124 stream.last_request_ms = now_ms;
2125 let entry = &mut stream.pending[position];
2126 entry.attempts += 1;
2127 if entry.attempts >= max_attempts {
2128 let message_id = entry.message_id;
2129 let fragment = entry.fragment;
2130 let placeholder = entry.handle;
2131 stream.pending.remove(position);
2132 if let Some(fragment) = fragment
2133 && let Some(slot_index) = self.pool.find_slot(&key, stream.epoch, message_id)
2134 && let Some(slot) = self.pool.slots[slot_index].as_mut()
2135 {
2136 slot.repair_exhausted |= 1u16 << fragment;
2137 }
2138 self.push_output(Output::Event(Event::RepairFinished {
2139 conversation: key.conversation,
2140 sender: key.sender,
2141 message_id,
2142 outcome: RepairOutcome::Exhausted,
2143 }));
2144 if fragment.is_none()
2147 && let Some(handle) = placeholder
2148 {
2149 self.flip_placeholder_unavailable(key, message_id, handle);
2150 }
2151 } else {
2152 entry.deadline_ms = now_ms + retry_ms;
2153 }
2154 }
2155 }
2156
2157 fn finalize_slot(
2160 &mut self,
2161 slot_index: usize,
2162 now_ms: u64,
2163 outcome: RepairOutcome,
2164 evicted: bool,
2165 ) {
2166 let Some(slot) = self.pool.slots[slot_index].as_ref() else {
2167 return;
2168 };
2169 let key = slot.stream;
2170 let id = slot.message_id;
2171 let announced = slot.announced;
2172 let handle = slot.handle;
2173 let complete = slot.is_complete();
2174
2175 if announced && !complete {
2176 let body = self.render_to_arena(slot_index, true);
2177 let slot = self.pool.slots[slot_index].as_ref().expect("occupied");
2178 let status = CompletionStatus::Partial {
2179 present: slot.present,
2180 count: slot.count,
2181 finalized: true,
2182 };
2183 self.emit_mutation(
2184 handle,
2185 MutationKind::UpdateBody {
2186 body,
2187 status,
2188 late: false,
2189 notify: false,
2190 },
2191 );
2192 }
2193 self.pool.close_slot(slot_index);
2194 if evicted {
2195 self.push_output(Output::Diagnostic(Diagnostic::ReassemblyEvicted {
2196 message_id: id,
2197 }));
2198 }
2199 if let Some(stream) = self.inbound.get_mut(&key) {
2200 stream.cancel_pending(id, None);
2201 stream.last_active_ms = now_ms;
2202 }
2203 self.push_output(Output::Event(Event::RepairFinished {
2204 conversation: key.conversation,
2205 sender: key.sender,
2206 message_id: id,
2207 outcome,
2208 }));
2209 }
2210
2211 fn encode_and_queue(
2216 &mut self,
2217 conversation: ConversationKey,
2218 handle: MessageHandle,
2219 template: &TextMessage<'_>,
2220 body: &[u8],
2221 message_id: u8,
2222 ) -> Result<(), ComposeError> {
2223 let destination = destination_for(&conversation);
2224
2225 let mut trial = *template;
2227 trial.body = &[];
2228 let mut buffer = [0u8; MAX_FRAME];
2229 let overhead = codec::encode(&trial, &mut buffer).map_err(ComposeError::Encode)?;
2230 let single_budget = MAX_FRAME.saturating_sub(overhead + 1);
2231
2232 let Some(plan) =
2233 FragmentPlan::plan(body.len(), single_budget).map_err(|_| ComposeError::TooLarge)?
2234 else {
2235 let mut message = *template;
2236 message.body = body;
2237 let len = codec::encode(&message, &mut buffer).map_err(ComposeError::Encode)?;
2238 let mut frame = heapless::Vec::new();
2239 let _ = frame.extend_from_slice(&buffer[..len]);
2240 self.queue_transmit(
2241 destination,
2242 Some(ArchiveKey {
2243 conversation,
2244 message_id,
2245 fragment: None,
2246 }),
2247 frame,
2248 Some((handle, None)),
2249 );
2250 return Ok(());
2251 };
2252
2253 for index in 0..plan.count {
2254 let range = plan.range(index);
2255 let mut message = if index == 0 {
2256 *template
2257 } else {
2258 let mut continuation = TextMessage::basic("");
2260 continuation.sequence = template.sequence;
2261 continuation
2262 };
2263 message.sequence = Some(MessageSequence {
2264 message_id,
2265 fragment: Some(Fragment {
2266 index,
2267 count: plan.count,
2268 }),
2269 });
2270 message.body = &body[range];
2271 let len = codec::encode(&message, &mut buffer).map_err(ComposeError::Encode)?;
2272 if len > MAX_FRAME {
2273 return Err(ComposeError::TooLarge);
2274 }
2275 let mut frame = heapless::Vec::new();
2276 let _ = frame.extend_from_slice(&buffer[..len]);
2277 self.queue_transmit(
2278 destination,
2279 Some(ArchiveKey {
2280 conversation,
2281 message_id,
2282 fragment: Some(index),
2283 }),
2284 frame,
2285 Some((handle, Some(index))),
2286 );
2287 }
2288 Ok(())
2289 }
2290
2291 fn archive_replacement(&mut self, conversation: ConversationKey, message_id: u8, body: &[u8]) {
2301 let mut template = TextMessage::basic("");
2302 template.sequence = Some(MessageSequence::unfragmented(message_id));
2303
2304 let mut buffer = [0u8; MAX_FRAME];
2305 let Ok(overhead) = codec::encode(&template, &mut buffer) else {
2306 return;
2307 };
2308 let single_budget = MAX_FRAME.saturating_sub(overhead + 1);
2309 let Ok(plan) = FragmentPlan::plan(body.len(), single_budget) else {
2310 return;
2311 };
2312
2313 let Some(plan) = plan else {
2314 let mut message = template;
2315 message.body = body;
2316 let Ok(len) = codec::encode(&message, &mut buffer) else {
2317 return;
2318 };
2319 let mut frame = heapless::Vec::new();
2320 let _ = frame.extend_from_slice(&buffer[..len]);
2321 self.push_output(Output::StoreArchive {
2322 key: ArchiveKey {
2323 conversation,
2324 message_id,
2325 fragment: None,
2326 },
2327 payload: frame,
2328 });
2329 return;
2330 };
2331
2332 for index in 0..plan.count {
2333 let range = plan.range(index);
2334 let mut message = TextMessage::basic("");
2335 message.sequence = Some(MessageSequence {
2336 message_id,
2337 fragment: Some(Fragment {
2338 index,
2339 count: plan.count,
2340 }),
2341 });
2342 message.body = &body[range];
2343 let Ok(len) = codec::encode(&message, &mut buffer) else {
2344 return;
2345 };
2346 let mut frame = heapless::Vec::new();
2347 let _ = frame.extend_from_slice(&buffer[..len]);
2348 self.push_output(Output::StoreArchive {
2349 key: ArchiveKey {
2350 conversation,
2351 message_id,
2352 fragment: Some(index),
2353 },
2354 payload: frame,
2355 });
2356 }
2357 }
2358
2359 fn queue_transmit(
2360 &mut self,
2361 destination: Destination,
2362 archive: Option<ArchiveKey>,
2363 payload: heapless::Vec<u8, MAX_FRAME>,
2364 track: Option<(MessageHandle, Option<u8>)>,
2365 ) -> u32 {
2366 let transmission_id = self.next_transmission;
2367 self.next_transmission = self.next_transmission.wrapping_add(1);
2368 if let Some((handle, fragment)) = track {
2369 if self.in_flight.is_full() {
2370 self.in_flight.remove(0);
2371 }
2372 let _ = self.in_flight.push(InFlightFrame {
2373 transmission_id,
2374 handle,
2375 fragment,
2376 archive,
2377 expects_ack: matches!(destination, Destination::Peer(_)),
2378 });
2379 }
2380 self.push_output(Output::Transmit(Transmission {
2381 transmission_id,
2382 destination,
2383 archive,
2384 payload,
2385 }));
2386 transmission_id
2387 }
2388
2389 fn wire_reference_for(
2395 &self,
2396 conversation: ConversationKey,
2397 handle: MessageHandle,
2398 ) -> Option<Regarding> {
2399 let multicast = conversation.uses_multicast_references();
2400 if let Some(stream) = self.outbound.get(&conversation)
2401 && let Some(id) = stream.refs.lookup_handle(handle)
2402 {
2403 return Some(if multicast {
2404 Regarding::Multicast {
2405 message_id: id,
2406 source_prefix: umsh_core::NodeHint([
2407 self.local_key.0[0],
2408 self.local_key.0[1],
2409 self.local_key.0[2],
2410 ]),
2411 }
2412 } else {
2413 Regarding::Unicast { message_id: id }
2414 });
2415 }
2416 for (key, stream) in self.inbound.iter() {
2417 if key.conversation != conversation {
2418 continue;
2419 }
2420 if let Some(id) = stream.refs.lookup_handle(handle) {
2421 return Some(if multicast {
2422 let prefix = key.sender.hint()?;
2423 Regarding::Multicast {
2424 message_id: id,
2425 source_prefix: prefix,
2426 }
2427 } else {
2428 Regarding::Unicast { message_id: id }
2429 });
2430 }
2431 }
2432 None
2433 }
2434
2435 fn resolve_regarding(
2438 &self,
2439 conversation: ConversationKey,
2440 target: RegardingRef,
2441 ) -> Result<(Regarding, ResolvedRef), ComposeError> {
2442 let (message_id, direction, sender_hint, epoch) = match target {
2443 RegardingRef::Handle(handle) => {
2444 let wire = self
2445 .wire_reference_for(conversation, handle)
2446 .ok_or(ComposeError::UnknownRegarding)?;
2447 return Ok((wire, ResolvedRef::Handle(handle)));
2448 }
2449 RegardingRef::Wire {
2450 message_id,
2451 direction,
2452 sender_hint,
2453 epoch,
2454 } => (message_id, direction, sender_hint, epoch),
2455 };
2456
2457 let sender = regarding_sender(conversation, direction, sender_hint)
2458 .ok_or(ComposeError::UnknownRegarding)?;
2459 match direction {
2460 Direction::Outbound => {
2461 let stream = self
2462 .outbound
2463 .get(&conversation)
2464 .ok_or(ComposeError::UnknownRegarding)?;
2465 if stream.announce_reset || epoch.is_some_and(|epoch| stream.epoch != epoch) {
2468 return Err(ComposeError::UnknownRegarding);
2469 }
2470 let delta = stream.next_id.wrapping_sub(message_id);
2472 if delta == 0 || delta > 128 {
2473 return Err(ComposeError::UnknownRegarding);
2474 }
2475 }
2476 Direction::Inbound => {
2477 let live = self.inbound.get(&StreamKey {
2478 conversation,
2479 sender,
2480 });
2481 if let Some(stream) = live
2482 && epoch.is_some_and(|epoch| stream.epoch != epoch)
2483 {
2484 return Err(ComposeError::UnknownRegarding);
2485 }
2486 }
2487 }
2488
2489 let wire = if conversation.uses_multicast_references() {
2490 let source_prefix = match direction {
2491 Direction::Outbound => umsh_core::NodeHint([
2492 self.local_key.0[0],
2493 self.local_key.0[1],
2494 self.local_key.0[2],
2495 ]),
2496 Direction::Inbound => sender.hint().ok_or(ComposeError::UnknownRegarding)?,
2497 };
2498 Regarding::Multicast {
2499 message_id,
2500 source_prefix,
2501 }
2502 } else {
2503 Regarding::Unicast { message_id }
2504 };
2505 Ok((
2508 wire,
2509 ResolvedRef::Unresolved(crate::model::WireRef::SenderScoped { sender, message_id }),
2510 ))
2511 }
2512
2513 fn resolved_from_wire(
2515 &self,
2516 conversation: ConversationKey,
2517 sender: Option<SenderScope>,
2518 regarding: Regarding,
2519 ) -> ResolvedRef {
2520 match regarding {
2521 Regarding::Multicast {
2522 message_id,
2523 source_prefix,
2524 } => {
2525 let local_prefix = umsh_core::NodeHint([
2526 self.local_key.0[0],
2527 self.local_key.0[1],
2528 self.local_key.0[2],
2529 ]);
2530 if source_prefix == local_prefix
2531 && let Some(stream) = self.outbound.get(&conversation)
2532 {
2533 let message_id = stream.edit_refs.resolve(message_id);
2534 if let Some(handle) = stream.refs.lookup(message_id) {
2535 return ResolvedRef::Handle(handle);
2536 }
2537 }
2538 let key = StreamKey {
2539 conversation,
2540 sender: SenderScope::ClaimedMember(source_prefix),
2541 };
2542 let message_id = self
2546 .inbound
2547 .get(&key)
2548 .map(|stream| stream.edit_refs.resolve(message_id))
2549 .unwrap_or(message_id);
2550 let unresolved = ResolvedRef::Unresolved(crate::model::WireRef::SenderScoped {
2551 sender: SenderScope::ClaimedMember(source_prefix),
2552 message_id,
2553 });
2554 match self.inbound.get(&key) {
2555 Some(stream) if !stream.collided => stream
2556 .refs
2557 .lookup(message_id)
2558 .map(ResolvedRef::Handle)
2559 .unwrap_or(unresolved),
2560 _ => unresolved,
2561 }
2562 }
2563 Regarding::Unicast { message_id } => {
2564 let inbound_key = sender.map(|sender| StreamKey {
2567 conversation,
2568 sender,
2569 });
2570 let inbound_stream = inbound_key.and_then(|key| self.inbound.get(&key));
2571 let outbound_stream = self.outbound.get(&conversation);
2572 let message_id = inbound_stream
2575 .map(|stream| stream.edit_refs.resolve(message_id))
2576 .filter(|resolved| *resolved != message_id)
2577 .or_else(|| outbound_stream.map(|stream| stream.edit_refs.resolve(message_id)))
2578 .unwrap_or(message_id);
2579 let inbound = inbound_stream.and_then(|stream| stream.refs.lookup(message_id));
2580 let outbound = outbound_stream.and_then(|stream| stream.refs.lookup(message_id));
2581 match (inbound, outbound) {
2582 (Some(handle), None) | (None, Some(handle)) => ResolvedRef::Handle(handle),
2583 _ => ResolvedRef::Unresolved(match sender {
2584 Some(sender) => crate::model::WireRef::SenderScoped { sender, message_id },
2585 None => crate::model::WireRef::RoomCanonical { message_id },
2586 }),
2587 }
2588 }
2589 }
2590 }
2591
2592 fn ensure_outbound(&mut self, conversation: ConversationKey, now_ms: u64) {
2597 if self.outbound.contains_key(&conversation) {
2598 return;
2599 }
2600 if self.outbound.len() == self.outbound.capacity()
2601 && let Some(oldest) = self
2602 .outbound
2603 .iter()
2604 .min_by_key(|(_, stream)| stream.last_active_ms)
2605 .map(|(key, _)| *key)
2606 {
2607 if let Some(stream) = self.outbound.remove(&oldest)
2610 && !stream.announce_reset
2611 {
2612 self.stash_checkpoint(oldest, stream.next_id, stream.epoch);
2613 }
2614 self.push_output(Output::Diagnostic(Diagnostic::StreamEvicted));
2615 }
2616 let cold = self
2617 .cold_checkpoints
2618 .iter()
2619 .position(|(key, _, _)| *key == conversation);
2620 let stream = match cold {
2621 Some(position) => {
2622 let (_, next_id, epoch) = self.cold_checkpoints.remove(position);
2623 OutboundStream {
2624 next_id,
2625 epoch,
2626 announce_reset: false,
2627 refs: Default::default(),
2628 edit_refs: Default::default(),
2629 last_active_ms: now_ms,
2630 }
2631 }
2632 None => OutboundStream::fresh(now_ms),
2633 };
2634 let _ = self.outbound.insert(conversation, stream);
2635 }
2636
2637 fn stash_checkpoint(&mut self, conversation: ConversationKey, next_id: u8, epoch: u16) {
2640 self.cold_checkpoints
2641 .retain(|(key, _, _)| *key != conversation);
2642 if self.cold_checkpoints.is_full() {
2643 self.cold_checkpoints.remove(0);
2644 }
2645 let _ = self.cold_checkpoints.push((conversation, next_id, epoch));
2646 }
2647
2648 fn ensure_inbound(&mut self, key: StreamKey, now_ms: u64) {
2649 if self.inbound.contains_key(&key) {
2650 return;
2651 }
2652 if self.inbound.len() == self.inbound.capacity()
2653 && let Some(oldest) = self
2654 .inbound
2655 .iter()
2656 .min_by_key(|(_, stream)| stream.last_active_ms)
2657 .map(|(key, _)| *key)
2658 {
2659 self.pool.drop_stream(&oldest);
2660 self.inbound.remove(&oldest);
2661 self.push_output(Output::Diagnostic(Diagnostic::StreamEvicted));
2662 }
2663 let _ = self.inbound.insert(key, InboundStream::new(now_ms));
2664 }
2665
2666 fn alloc_handle(&mut self) -> MessageHandle {
2667 let handle = MessageHandle(self.next_handle);
2668 self.next_handle = self.next_handle.wrapping_add(1);
2669 handle
2670 }
2671
2672 fn emit_gap_placeholder(&mut self, key: StreamKey, id: u8, epoch: u16) -> MessageHandle {
2676 let handle = self.alloc_handle();
2677 self.emit_mutation(
2678 handle,
2679 MutationKind::Insert {
2680 conversation: key.conversation,
2681 sender: key.sender,
2682 direction: Direction::Inbound,
2683 message_type: MessageType::Basic,
2684 wire_id: Some(id),
2685 epoch,
2686 client_token: None,
2687 sender_handle: None,
2688 regarding: None,
2689 bg_color: None,
2690 text_color: None,
2691 body: BodyRef { offset: 0, len: 0 },
2692 status: CompletionStatus::Complete,
2693 presence: Presence::GapPending,
2694 late: false,
2695 notify: false,
2696 },
2697 );
2698 if let Some(stream) = self.inbound.get_mut(&key) {
2699 stream.refs.record(id, handle);
2700 }
2701 handle
2702 }
2703
2704 fn fill_gap_with_edit(
2711 &mut self,
2712 envelope: &Envelope,
2713 key: StreamKey,
2714 original_id: u8,
2715 body: &[u8],
2716 ) -> bool {
2717 let Some(placeholder) = self.inbound.get(&key).and_then(|stream| {
2718 stream
2719 .pending
2720 .iter()
2721 .find(|pending| pending.message_id == original_id && pending.fragment.is_none())
2722 .and_then(|pending| pending.handle)
2723 }) else {
2724 return false;
2725 };
2726
2727 if let Some(stream) = self.inbound.get_mut(&key) {
2728 stream.cancel_pending(original_id, None);
2729 stream.seen.insert(original_id);
2730 }
2731 self.push_output(Output::Event(Event::RepairFinished {
2732 conversation: key.conversation,
2733 sender: key.sender,
2734 message_id: original_id,
2735 outcome: RepairOutcome::Repaired,
2736 }));
2737
2738 if body.is_empty() {
2739 self.emit_mutation(
2741 placeholder,
2742 MutationKind::Delete {
2743 conversation: key.conversation,
2744 original: ResolvedRef::Handle(placeholder),
2745 },
2746 );
2747 return true;
2748 }
2749
2750 let text = core::str::from_utf8(body).unwrap_or("");
2751 let body_ref = self
2752 .arena_store(text)
2753 .unwrap_or(BodyRef { offset: 0, len: 0 });
2754 let epoch = self
2755 .inbound
2756 .get(&key)
2757 .map(|stream| stream.epoch)
2758 .unwrap_or(0);
2759 self.emit_mutation(
2760 placeholder,
2761 MutationKind::Insert {
2762 conversation: envelope.conversation,
2763 sender: envelope.sender,
2764 direction: Direction::Inbound,
2765 message_type: MessageType::Basic,
2766 wire_id: Some(original_id),
2767 epoch,
2768 client_token: None,
2769 sender_handle: None,
2770 regarding: None,
2771 bg_color: None,
2772 text_color: None,
2773 body: body_ref,
2774 status: CompletionStatus::Complete,
2775 presence: Presence::Present,
2776 late: true,
2777 notify: true,
2778 },
2779 );
2780 self.emit_mutation(
2782 placeholder,
2783 MutationKind::Edit {
2784 conversation: key.conversation,
2785 original: ResolvedRef::Handle(placeholder),
2786 body: body_ref,
2787 },
2788 );
2789 true
2790 }
2791
2792 fn flip_placeholder_unavailable(&mut self, key: StreamKey, id: u8, handle: MessageHandle) {
2796 let epoch = self
2797 .inbound
2798 .get(&key)
2799 .map(|stream| stream.epoch)
2800 .unwrap_or(0);
2801 self.emit_mutation(
2802 handle,
2803 MutationKind::Insert {
2804 conversation: key.conversation,
2805 sender: key.sender,
2806 direction: Direction::Inbound,
2807 message_type: MessageType::Basic,
2808 wire_id: Some(id),
2809 epoch,
2810 client_token: None,
2811 sender_handle: None,
2812 regarding: None,
2813 bg_color: None,
2814 text_color: None,
2815 body: BodyRef { offset: 0, len: 0 },
2816 status: CompletionStatus::Complete,
2817 presence: Presence::Unavailable,
2818 late: false,
2819 notify: false,
2820 },
2821 );
2822 if let Some(stream) = self.inbound.get_mut(&key) {
2823 stream.refs.retire(id);
2824 }
2825 }
2826
2827 fn emit_mutation(&mut self, handle: MessageHandle, kind: MutationKind) {
2828 let revision = self.revision;
2829 self.revision = self.revision.wrapping_add(1);
2830 self.push_output(Output::StoreMessage(MessageMutation {
2831 handle,
2832 revision,
2833 kind,
2834 }));
2835 }
2836
2837 fn push_output(&mut self, output: Output) {
2838 if self.outputs.push_back(output).is_err() {
2839 self.lost_outputs = self.lost_outputs.wrapping_add(1);
2840 }
2841 }
2842
2843 fn arena_store(&mut self, text: &str) -> Option<BodyRef> {
2844 let bytes = text.as_bytes();
2845 if self.arena_used + bytes.len() > ARENA_SIZE || bytes.len() > u16::MAX as usize {
2846 self.push_output(Output::Diagnostic(Diagnostic::OutputOverflow));
2847 return None;
2848 }
2849 let offset = self.arena_used;
2850 self.arena[offset..offset + bytes.len()].copy_from_slice(bytes);
2851 self.arena_used += bytes.len();
2852 Some(BodyRef {
2853 offset: offset as u16,
2854 len: bytes.len() as u16,
2855 })
2856 }
2857
2858 fn render_to_arena(&mut self, slot_index: usize, final_render: bool) -> BodyRef {
2860 let mut scratch = [0u8; REASSEMBLED_BODY_MAX + 64];
2861 let result = fragment::render_slot(
2862 &self.pool,
2863 slot_index,
2864 &self.config.sentinels,
2865 final_render,
2866 &mut scratch,
2867 );
2868 if result.complete && result.had_invalid {
2869 let message_id = self.pool.slots[slot_index]
2872 .as_ref()
2873 .expect("occupied")
2874 .message_id;
2875 self.push_output(Output::Diagnostic(Diagnostic::ReassembledInvalidUtf8 {
2876 message_id,
2877 }));
2878 }
2879 let text = core::str::from_utf8(&scratch[..result.len]).unwrap_or("");
2880 self.arena_store(text)
2881 .unwrap_or(BodyRef { offset: 0, len: 0 })
2882 }
2883}
2884
2885pub fn destination_for(conversation: &ConversationKey) -> Destination {
2889 match conversation {
2890 ConversationKey::Direct { peer } => Destination::Peer(*peer),
2891 ConversationKey::Room { room } => Destination::Peer(*room),
2892 ConversationKey::ChannelGroup { channel } => Destination::Channel(*channel),
2893 ConversationKey::ChannelDirect { channel, peer } => Destination::ChannelPeer {
2894 channel: *channel,
2895 peer: *peer,
2896 },
2897 }
2898}