1use super::*;
2
3impl ProtocolEngine {
4 pub fn observe_invite_event(&mut self, event: &Event) -> anyhow::Result<ProtocolRetryBatch> {
5 Ok(self.observe_invite_event_with_outcome(event)?.retry_batch)
6 }
7
8 pub fn observe_invite_event_with_outcome(
9 &mut self,
10 event: &Event,
11 ) -> anyhow::Result<ProtocolMetadataIngestionResult> {
12 let mut invite = parse_invite_event(event)?;
13 let invite_owner = invite
14 .inviter_owner_pubkey
15 .or_else(|| self.verified_roster_owner_for_device(invite.inviter_device_pubkey))
16 .unwrap_or_else(|| NdrOwnerPubkey::from_bytes(invite.inviter_device_pubkey.to_bytes()));
17 if invite.inviter_owner_pubkey.is_none() {
18 invite.inviter_owner_pubkey = Some(invite_owner);
19 }
20 if invite.inviter_device_pubkey == self.local_device {
21 return Ok(ProtocolMetadataIngestionResult::default());
22 }
23 let session_checkpoint = self.session_manager.clone();
24 self.session_manager
25 .observe_device_invite(invite_owner, invite)?;
26 if self.session_manager.snapshot() == session_checkpoint.snapshot() {
30 return Ok(ProtocolMetadataIngestionResult::default());
31 }
32 let pending_inbound_checkpoint = self.pending_inbound.clone();
33 let pending_group_fanouts_checkpoint = self.pending_group_fanouts.clone();
34 let pending_group_pairwise_checkpoint = self.pending_group_pairwise_payloads.clone();
35 self.invalidate_known_message_author_cache();
36 self.wake_pending_protocol_for_owner(invite_owner);
37 if let Err(error) = self.persist() {
38 self.session_manager = session_checkpoint;
39 self.invalidate_pending_inbound_retry_deadline();
40 self.pending_inbound = pending_inbound_checkpoint;
41 self.invalidate_pending_group_fanout_retry_deadline();
42 self.pending_group_fanouts = pending_group_fanouts_checkpoint;
43 self.pending_group_pairwise_payloads = pending_group_pairwise_checkpoint;
44 self.invalidate_known_message_author_cache();
45 return Err(error);
46 }
47 Ok(ProtocolMetadataIngestionResult {
48 changed: true,
49 retry_batch: self.retry_pending_protocol(NdrUnixSeconds(event.created_at.as_secs()))?,
50 })
51 }
52
53 pub fn observe_invite_response_event(
54 &mut self,
55 event: &Event,
56 ) -> anyhow::Result<ProtocolRetryBatch> {
57 let Some(local_invite_recipient) = self
58 .session_manager
59 .snapshot()
60 .local_invite
61 .as_ref()
62 .map(|invite| invite.inviter_ephemeral_public_key)
63 else {
64 return Ok(ProtocolRetryBatch::default());
65 };
66 let envelope = parse_invite_response_event(event)?;
67 if envelope.recipient != local_invite_recipient {
68 return Ok(ProtocolRetryBatch::default());
69 }
70 let checkpoint = self.state_checkpoint();
71 let mut rng = OsRng;
72 let mut ctx = ProtocolContext::new(NdrUnixSeconds(event.created_at.as_secs()), &mut rng);
73 let processed = match self
74 .session_manager
75 .observe_invite_response(&mut ctx, &envelope)
76 {
77 Ok(processed) => processed,
78 Err(NdrError::Domain(DomainError::InviteAlreadyUsed)) => {
79 return Ok(ProtocolRetryBatch::default());
80 }
81 Err(error) => return Err(error.into()),
82 };
83 self.invalidate_known_message_author_cache();
84 if let Some(processed) = processed.as_ref() {
85 self.wake_pending_protocol_for_owner(
86 processed
87 .claimed_owner_pubkey
88 .unwrap_or(processed.owner_pubkey),
89 );
90 }
91 if let Some(processed) = processed.as_ref() {
92 if let Some(invite) = self.local_invite() {
93 let proof_batch = match self.ingest_invite_response_owner_proof(
94 &invite,
95 event,
96 public_owner(
97 processed
98 .claimed_owner_pubkey
99 .unwrap_or(processed.owner_pubkey),
100 )?,
101 public_device(processed.device_pubkey)?,
102 ) {
103 Ok(batch) => batch,
104 Err(error) => {
105 self.restore_checkpoint(checkpoint);
106 self.invalidate_known_message_author_cache();
107 return Err(error);
108 }
109 };
110 if !proof_batch.is_empty() {
111 return Ok(proof_batch);
112 }
113 }
114 }
115 if let Err(error) = self.persist() {
116 self.restore_checkpoint(checkpoint);
117 self.invalidate_known_message_author_cache();
118 return Err(error);
119 }
120 self.retry_pending_protocol(ctx.now)
121 }
122
123 pub fn accept_invite(
124 &mut self,
125 invite: &Invite,
126 owner_pubkey_hint: Option<PublicKey>,
127 ) -> anyhow::Result<ProtocolAcceptInviteOutcome> {
128 let invite_owner = resolve_invite_owner(invite, owner_pubkey_hint)?;
129 let inviter_device = public_device(invite.inviter_device_pubkey)?;
130 let owner = ndr_owner(invite_owner);
131
132 if invite_owner != inviter_device {
133 let Some(authorized) = self.invite_owner_app_keys_membership(owner, inviter_device)
134 else {
135 return Ok(ProtocolAcceptInviteOutcome::Blocked(
136 ProtocolAcceptInviteBlock::MissingOwnerRoster {
137 owner_pubkey: invite_owner,
138 device_pubkey: inviter_device,
139 },
140 ));
141 };
142 if !authorized {
143 return Ok(ProtocolAcceptInviteOutcome::Blocked(
144 ProtocolAcceptInviteBlock::UnauthorizedDevice {
145 owner_pubkey: invite_owner,
146 device_pubkey: inviter_device,
147 },
148 ));
149 }
150 }
151
152 let mut invite = invite.clone();
153 invite.inviter_owner_pubkey = Some(owner);
154 let checkpoint = self.state_checkpoint();
155 self.session_manager
156 .observe_device_invite(owner, invite.clone())?;
157 if invite_owner == inviter_device {
158 let has_roster = self
159 .session_manager
160 .snapshot()
161 .users
162 .into_iter()
163 .find(|user| user.owner_pubkey == owner)
164 .and_then(|user| user.roster)
165 .is_some();
166 if !has_roster {
167 self.session_manager.observe_peer_roster(
168 owner,
169 DeviceRoster::new(
170 NdrUnixSeconds(unix_now().get()),
171 vec![AuthorizedDevice::new(
172 invite.inviter_device_pubkey,
173 invite.created_at,
174 )],
175 ),
176 );
177 }
178 }
179 self.invalidate_known_message_author_cache();
180 let now = unix_now();
181 let result = (|| -> anyhow::Result<ProtocolAcceptInviteResult> {
182 let mut rng = OsRng;
183 let mut ctx = ProtocolContext::new(NdrUnixSeconds(now.get()), &mut rng);
184 let (mut session, response) = invite.accept_with_owner_context(
185 &mut ctx,
186 self.local_device,
187 self.local_device_secret,
188 Some(self.local_owner),
189 )?;
190
191 let response_event = self.cached_invite_response_with_owner_proof(&response)?;
195 let mut typing = pairwise_codec::typing_event(
196 self.owner_pubkey,
197 pairwise_codec::EncodeOptions::new(now.get(), current_unix_millis())
198 .with_expiration(1),
199 )?;
200 typing.ensure_id();
201 let typing_payload = serde_json::to_vec(&typing)?;
202 let plan = session.plan_send(&typing_payload, ctx.now)?;
203 let envelope = session.apply_send(plan).envelope;
204 let typing_delivery = Delivery {
205 owner_pubkey: owner,
206 device_pubkey: invite.inviter_device_pubkey,
207 envelope,
208 };
209 let typing_event = message_event_for_delivery(&typing_delivery)?;
210 let typing_carrier = pairwise_carrier(&typing_delivery)?;
211
212 self.session_manager.import_session_state(
213 owner,
214 invite.inviter_device_pubkey,
215 session.state,
216 ctx.now,
217 );
218 self.invalidate_known_message_author_cache();
219 self.persist()?;
220
221 let chat_id = invite_owner.to_hex();
222 Ok(ProtocolAcceptInviteResult {
223 owner_pubkey: invite_owner,
224 inviter_device_pubkey: inviter_device,
225 device_id: inviter_device.to_hex(),
226 effects: vec![
227 ProtocolEffect::Publish(ProtocolPublish {
228 authored_at_secs: Some(now.get()),
229 event: response_event,
230 chat_id: chat_id.clone(),
231 inner_event_id: None,
232 carrier: ProtocolCarrier::Unmarked,
233 }),
234 ProtocolEffect::Publish(ProtocolPublish {
235 authored_at_secs: Some(now.get()),
236 event: typing_event,
237 chat_id,
238 inner_event_id: None,
239 carrier: typing_carrier,
240 }),
241 ],
242 })
243 })();
244
245 match result {
246 Ok(result) => Ok(ProtocolAcceptInviteOutcome::Accepted(result)),
247 Err(error) => {
248 self.restore_checkpoint(checkpoint);
249 Err(error)
250 }
251 }
252 }
253
254 pub fn import_session_state(
255 &mut self,
256 peer_pubkey: PublicKey,
257 device_id: Option<String>,
258 state: SessionState,
259 now: UnixSeconds,
260 ) -> anyhow::Result<ProtocolRetryBatch> {
261 let device_pubkey = device_id
262 .as_deref()
263 .and_then(|value| PublicKey::parse(value).ok())
264 .map(ndr_device)
265 .unwrap_or_else(|| ndr_device(peer_pubkey));
266 self.session_manager.import_session_state(
267 ndr_owner(peer_pubkey),
268 device_pubkey,
269 state,
270 NdrUnixSeconds(now.get()),
271 );
272 self.invalidate_known_message_author_cache();
273 self.wake_pending_protocol_for_owner(ndr_owner(peer_pubkey));
274 self.persist()?;
275 self.retry_pending_protocol(NdrUnixSeconds(now.get()))
276 }
277
278 pub fn create_group(
279 &mut self,
280 name: String,
281 member_owners: Vec<PublicKey>,
282 now: UnixSeconds,
283 ) -> anyhow::Result<ProtocolGroupSendResult> {
284 self.with_state_checkpoint(|engine| {
285 let mut rng = OsRng;
286 let mut ctx = ProtocolContext::new(NdrUnixSeconds(now.get()), &mut rng);
287 let result = engine.group_manager.create_group_with_protocol(
288 &mut engine.session_manager,
289 &mut ctx,
290 name,
291 member_owners.into_iter().map(ndr_owner).collect(),
292 GroupProtocol::sender_key_v1(),
293 )?;
294 let mut output = engine.protocol_group_send_from_prepared(&result.prepared, None)?;
295 output.snapshot = Some(result.group);
296 engine.persist()?;
297 Ok(output)
298 })
299 }
300
301 fn ensure_supported_group_protocol(&self, group_id: &str) -> anyhow::Result<()> {
302 if self
303 .group_manager
304 .group(group_id)
305 .is_some_and(|group| !group.protocol.is_sender_key_v1())
306 {
307 anyhow::bail!("group `{group_id}` uses an unsupported legacy group protocol");
308 }
309 Ok(())
310 }
311
312 pub fn update_group_name(
313 &mut self,
314 group_id: &str,
315 name: String,
316 ) -> anyhow::Result<ProtocolGroupSendResult> {
317 self.with_state_checkpoint(|engine| {
318 engine.ensure_supported_group_protocol(group_id)?;
319 let mut rng = OsRng;
320 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
321 let prepared = engine.group_manager.update_name(
322 &mut engine.session_manager,
323 &mut ctx,
324 group_id,
325 name,
326 )?;
327 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
328 output.snapshot = engine.group_manager.group(group_id);
329 engine.persist()?;
330 Ok(output)
331 })
332 }
333
334 pub fn update_group_picture(
335 &mut self,
336 group_id: &str,
337 picture: Option<String>,
338 ) -> anyhow::Result<ProtocolGroupSendResult> {
339 self.with_state_checkpoint(|engine| {
340 engine.ensure_supported_group_protocol(group_id)?;
341 let mut rng = OsRng;
342 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
343 let prepared = engine.group_manager.update_picture(
344 &mut engine.session_manager,
345 &mut ctx,
346 group_id,
347 picture,
348 )?;
349 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
350 output.snapshot = engine.group_manager.group(group_id);
351 engine.persist()?;
352 Ok(output)
353 })
354 }
355
356 pub fn update_group_about(
357 &mut self,
358 group_id: &str,
359 about: Option<String>,
360 ) -> anyhow::Result<ProtocolGroupSendResult> {
361 self.with_state_checkpoint(|engine| {
362 engine.ensure_supported_group_protocol(group_id)?;
363 let mut rng = OsRng;
364 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
365 let prepared = engine.group_manager.update_about(
366 &mut engine.session_manager,
367 &mut ctx,
368 group_id,
369 about,
370 )?;
371 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
372 output.snapshot = engine.group_manager.group(group_id);
373 engine.persist()?;
374 Ok(output)
375 })
376 }
377
378 pub fn add_group_members(
379 &mut self,
380 group_id: &str,
381 members: Vec<PublicKey>,
382 ) -> anyhow::Result<ProtocolGroupSendResult> {
383 self.with_state_checkpoint(|engine| {
384 engine.ensure_supported_group_protocol(group_id)?;
385 let mut rng = OsRng;
386 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
387 let prepared = engine.group_manager.add_members(
388 &mut engine.session_manager,
389 &mut ctx,
390 group_id,
391 members.into_iter().map(ndr_owner).collect(),
392 )?;
393 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
394 output.snapshot = engine.group_manager.group(group_id);
395 engine.persist()?;
396 Ok(output)
397 })
398 }
399
400 pub fn admit_group_invite_member(
404 &mut self,
405 group_id: &str,
406 token: &str,
407 member: PublicKey,
408 max_uses: Option<u32>,
409 ) -> anyhow::Result<ProtocolGroupSendResult> {
410 anyhow::ensure!(
411 token.len() == 64 && token.bytes().all(|c| c.is_ascii_hexdigit()),
412 "Invalid invite token"
413 );
414 anyhow::ensure!(
415 max_uses.is_none_or(|n| (1..=10000).contains(&n)),
416 "Invalid invite limit"
417 );
418 self.with_state_checkpoint(|engine| {
419 engine.ensure_supported_group_protocol(group_id)?;
420 let group = engine
421 .group_manager
422 .group(group_id)
423 .ok_or_else(|| anyhow::anyhow!("Group unavailable"))?;
424 anyhow::ensure!(
425 group.admins.contains(&engine.local_owner)
426 && group.members.contains(&engine.local_owner),
427 "Admin required"
428 );
429 let member_owner = ndr_owner(member);
430 let key = format!("{group_id}:{token}");
431 let used = engine.group_invite_uses.get(&key);
432 anyhow::ensure!(
433 !used.is_some_and(|owners| owners.contains(&member_owner)),
434 "Invite already used"
435 );
436 anyhow::ensure!(
437 max_uses.is_none_or(|limit| used.map_or(0, BTreeSet::len) < limit as usize),
438 "Invite is full"
439 );
440 anyhow::ensure!(!group.members.contains(&member_owner), "Already a member");
441 let mut rng = OsRng;
442 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
443 let prepared = engine.group_manager.add_members(
444 &mut engine.session_manager,
445 &mut ctx,
446 group_id,
447 vec![member_owner],
448 )?;
449 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
450 output.snapshot = engine.group_manager.group(group_id);
451 engine
452 .group_invite_uses
453 .entry(key)
454 .or_default()
455 .insert(member_owner);
456 engine.persist_now()?;
457 Ok(output)
458 })
459 }
460
461 pub fn group_invite_use_count(&self, group_id: &str, token: &str) -> usize {
462 self.group_invite_uses
463 .get(&format!("{group_id}:{token}"))
464 .map_or(0, BTreeSet::len)
465 }
466
467 pub fn group_snapshot(&self, group_id: &str) -> Option<GroupSnapshot> {
468 self.group_manager.group(group_id)
469 }
470
471 pub fn group_invite_used_by(&self, group_id: &str, token: &str, member: PublicKey) -> bool {
472 self.group_invite_uses
473 .get(&format!("{group_id}:{token}"))
474 .is_some_and(|owners| owners.contains(&ndr_owner(member)))
475 }
476
477 pub fn remove_group_member(
478 &mut self,
479 group_id: &str,
480 member: PublicKey,
481 ) -> anyhow::Result<ProtocolGroupSendResult> {
482 self.with_state_checkpoint(|engine| {
483 engine.ensure_supported_group_protocol(group_id)?;
484 let mut rng = OsRng;
485 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
486 let prepared = engine.group_manager.remove_members(
487 &mut engine.session_manager,
488 &mut ctx,
489 group_id,
490 vec![ndr_owner(member)],
491 )?;
492 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
493 output.snapshot = engine.group_manager.group(group_id);
494 engine.persist()?;
495 Ok(output)
496 })
497 }
498
499 pub fn set_group_admin(
500 &mut self,
501 group_id: &str,
502 member: PublicKey,
503 is_admin: bool,
504 ) -> anyhow::Result<ProtocolGroupSendResult> {
505 self.with_state_checkpoint(|engine| {
506 engine.ensure_supported_group_protocol(group_id)?;
507 let mut rng = OsRng;
508 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
509 let prepared = if is_admin {
510 engine.group_manager.add_admins(
511 &mut engine.session_manager,
512 &mut ctx,
513 group_id,
514 vec![ndr_owner(member)],
515 )?
516 } else {
517 engine.group_manager.remove_admins(
518 &mut engine.session_manager,
519 &mut ctx,
520 group_id,
521 vec![ndr_owner(member)],
522 )?
523 };
524 let mut output = engine.protocol_group_send_from_prepared(&prepared, None)?;
525 output.snapshot = engine.group_manager.group(group_id);
526 engine.persist()?;
527 Ok(output)
528 })
529 }
530
531 pub fn send_group_payload(
532 &mut self,
533 group_id: &str,
534 payload: Vec<u8>,
535 inner_event_id: Option<String>,
536 ) -> anyhow::Result<ProtocolGroupSendResult> {
537 self.with_state_checkpoint(|engine| {
538 engine.ensure_supported_group_protocol(group_id)?;
539 let mut rng = OsRng;
540 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
541 let prepared = engine.group_manager.send_message(
542 &mut engine.session_manager,
543 &mut ctx,
544 group_id,
545 payload,
546 )?;
547 let message_id = inner_event_id.clone();
548 let mut output = engine.protocol_group_send_from_prepared(&prepared, inner_event_id)?;
549 output.snapshot = engine.group_manager.group(group_id);
550 engine.prepare_private_group_wakes(group_id, &mut output, message_id.clone())?;
551 let private_ids = output
555 .effects
556 .iter()
557 .filter_map(|effect| {
558 let ProtocolEffect::Publish(publish) = effect;
559 (publish.carrier != ProtocolCarrier::SharedGroup)
560 .then_some(publish.event.id.to_hex())
561 })
562 .collect::<HashSet<_>>();
563 output.effects.retain(|effect| {
564 let ProtocolEffect::Publish(publish) = effect;
565 private_ids.contains(&publish.event.id.to_hex())
566 });
567 output.event_ids.retain(|id| private_ids.contains(id));
568 output.has_pending_deliveries = engine.pending_group_fanouts.iter().any(|pending| {
569 pending.group_id == group_id && pending.inner_event_id == message_id
570 });
571 engine.retain_group_publications(&output.effects, None)?;
572 output.message_id = message_id;
573 engine.persist_now()?;
576 Ok(output)
577 })
578 }
579
580 pub fn send_direct_text(
581 &mut self,
582 peer_pubkey: PublicKey,
583 chat_id: &str,
584 text: &str,
585 expires_at_secs: Option<u64>,
586 now: UnixSeconds,
587 ) -> anyhow::Result<ProtocolDirectSendResult> {
588 self.send_direct_text_created_at(peer_pubkey, chat_id, text, expires_at_secs, now, now)
589 }
590
591 #[allow(clippy::too_many_arguments)]
593 pub fn send_direct_text_created_at(
594 &mut self,
595 peer_pubkey: PublicKey,
596 chat_id: &str,
597 text: &str,
598 expires_at_secs: Option<u64>,
599 created_at: UnixSeconds,
600 now: UnixSeconds,
601 ) -> anyhow::Result<ProtocolDirectSendResult> {
602 let now_ms = current_unix_millis();
603 let mut options = pairwise_codec::EncodeOptions::new(created_at.get(), now_ms);
604 if let Some(expires_at_secs) = expires_at_secs {
605 options = options.with_expiration(expires_at_secs);
606 }
607 let rumor = pairwise_codec::message_event(self.owner_pubkey, text.to_string(), options)?;
608 self.send_direct_unsigned_event(peer_pubkey, chat_id, rumor, now)
609 }
610
611 pub fn send_direct_unsigned_event(
612 &mut self,
613 peer_pubkey: PublicKey,
614 chat_id: &str,
615 mut rumor: UnsignedEvent,
616 now: UnixSeconds,
617 ) -> anyhow::Result<ProtocolDirectSendResult> {
618 rumor.ensure_id();
619 let message_id = rumor
620 .id
621 .as_ref()
622 .map(ToString::to_string)
623 .unwrap_or_default();
624 let remote_payload = serde_json::to_vec(&rumor)?;
625 let sibling_payload = local_sibling_payload(peer_pubkey, &remote_payload)?;
626 self.with_state_checkpoint(|engine| {
627 let self_chat = peer_pubkey == engine.owner_pubkey;
628 let (mut event_ids, mut effects) = if self_chat {
629 (Vec::new(), Vec::new())
630 } else {
631 engine.queue_remote_payload(
632 peer_pubkey,
633 chat_id,
634 remote_payload,
635 Some(message_id.clone()),
636 &message_id,
637 now,
638 )?
639 };
640 let siblings: BTreeSet<_> = engine
641 .session_manager
642 .roster(engine.local_owner)
643 .into_iter()
644 .flat_map(|roster| roster.devices())
645 .map(|entry| entry.device_pubkey)
646 .filter(|device| *device != engine.local_device)
647 .collect();
648 let needs_sibling_delivery = self_chat && !siblings.is_empty();
649 let sibling_effects = engine.queue_local_sibling_payload(
650 chat_id,
651 sibling_payload,
652 &message_id,
653 now,
654 Some(siblings),
655 );
656 event_ids.extend(
658 sibling_effects
659 .iter()
660 .filter(|ProtocolEffect::Publish(publish)| {
661 !self_chat || publish.inner_event_id.is_some()
662 })
663 .map(|ProtocolEffect::Publish(publish)| publish.event.id.to_string()),
664 );
665 anyhow::ensure!(
666 !needs_sibling_delivery || !event_ids.is_empty(),
667 "no linked device delivery prepared"
668 );
669 effects.extend(sibling_effects);
670 engine.persist()?;
671 Ok(ProtocolDirectSendResult {
672 message_id,
673 event_ids,
674 effects,
675 })
676 })
677 }
678
679 pub fn send_direct_unsigned_event_to_peer_only(
680 &mut self,
681 peer_pubkey: PublicKey,
682 chat_id: &str,
683 mut rumor: UnsignedEvent,
684 now: UnixSeconds,
685 ) -> anyhow::Result<ProtocolDirectSendResult> {
686 rumor.ensure_id();
687 let message_id = rumor
688 .id
689 .as_ref()
690 .map(ToString::to_string)
691 .unwrap_or_default();
692 let remote_payload = serde_json::to_vec(&rumor)?;
693 self.with_state_checkpoint(|engine| {
694 engine.send_direct_remote_payload_inner(
695 peer_pubkey,
696 chat_id,
697 remote_payload,
698 Some(message_id.clone()),
699 message_id,
700 now,
701 )
702 })
703 }
704
705 pub fn send_local_sibling_unsigned_event(
706 &mut self,
707 conversation_owner: PublicKey,
708 chat_id: &str,
709 mut rumor: UnsignedEvent,
710 now: UnixSeconds,
711 ) -> anyhow::Result<ProtocolDirectSendResult> {
712 rumor.ensure_id();
713 let message_id = rumor
714 .id
715 .as_ref()
716 .map(ToString::to_string)
717 .unwrap_or_default();
718 let payload = serde_json::to_vec(&rumor)?;
719 let sibling_payload = local_sibling_payload(conversation_owner, &payload)?;
720 self.with_state_checkpoint(|engine| {
721 let effects = engine.queue_local_sibling_payload(
722 chat_id,
723 sibling_payload,
724 &message_id,
725 now,
726 None,
727 );
728 engine.persist()?;
729 Ok(ProtocolDirectSendResult {
730 message_id,
731 event_ids: effects
732 .iter()
733 .map(|effect| match effect {
734 ProtocolEffect::Publish(publish) => publish.event.id.to_string(),
735 })
736 .collect(),
737 effects,
738 })
739 })
740 }
741
742 fn queue_local_sibling_payload(
743 &mut self,
744 chat_id: &str,
745 sibling_payload: Vec<u8>,
746 message_id: &str,
747 now: UnixSeconds,
748 eligible_devices: Option<BTreeSet<NdrDevicePubkey>>,
749 ) -> Vec<ProtocolEffect> {
750 let existing = self
751 .pending_local_sibling_sends
752 .iter()
753 .position(|pending| pending.message_id == message_id && pending.chat_id == chat_id)
754 .map(|index| self.pending_local_sibling_sends.remove(index));
755 let mut pending = existing.unwrap_or_else(|| ProtocolPendingLocalSiblingSend {
756 eligible_devices,
757 chat_id: chat_id.to_string(),
758 payload: sibling_payload,
759 message_id: message_id.to_string(),
760 completed_devices: BTreeSet::new(),
761 created_at_secs: now.get(),
762 next_retry_at_secs: now.get(),
763 });
764 let sessions = self.session_manager.clone();
765 let (effects, complete) = match self
766 .prepare_pending_local_sibling_send(&mut pending, NdrUnixSeconds(now.get()))
767 {
768 Ok(result) => result,
769 Err(_) => {
770 self.session_manager = sessions;
771 (Vec::new(), false)
772 }
773 };
774 if !complete {
775 self.pending_local_sibling_sends.push(pending);
776 }
777 effects
778 }
779
780 fn send_direct_remote_payload_inner(
781 &mut self,
782 peer_pubkey: PublicKey,
783 chat_id: &str,
784 remote_payload: Vec<u8>,
785 inner_event_id: Option<String>,
786 message_id: String,
787 now: UnixSeconds,
788 ) -> anyhow::Result<ProtocolDirectSendResult> {
789 let (event_ids, effects) = self.queue_remote_payload(
790 peer_pubkey,
791 chat_id,
792 remote_payload,
793 inner_event_id,
794 &message_id,
795 now,
796 )?;
797 self.persist()?;
798 Ok(ProtocolDirectSendResult {
799 message_id,
800 event_ids,
801 effects,
802 })
803 }
804}