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 if output.snapshot.as_ref().is_some_and(|group| {
551 group.members == [engine.local_owner]
552 && engine
553 .session_manager
554 .roster(engine.local_owner)
555 .is_some_and(|roster| {
556 roster
557 .devices()
558 .iter()
559 .all(|device| device.device_pubkey == engine.local_device)
560 })
561 }) {
562 output.effects.clear();
564 output.event_ids.clear();
565 }
566 output.has_pending_deliveries = engine.pending_group_fanouts.iter().any(|pending| {
567 pending.group_id == group_id && pending.inner_event_id == message_id
568 });
569 engine.retain_group_publications(&output.effects, None)?;
570 output.message_id = message_id;
571 engine.persist_now()?;
574 Ok(output)
575 })
576 }
577
578 pub fn send_direct_text(
579 &mut self,
580 peer_pubkey: PublicKey,
581 chat_id: &str,
582 text: &str,
583 expires_at_secs: Option<u64>,
584 now: UnixSeconds,
585 ) -> anyhow::Result<ProtocolDirectSendResult> {
586 self.send_direct_text_created_at(peer_pubkey, chat_id, text, expires_at_secs, now, now)
587 }
588
589 #[allow(clippy::too_many_arguments)]
591 pub fn send_direct_text_created_at(
592 &mut self,
593 peer_pubkey: PublicKey,
594 chat_id: &str,
595 text: &str,
596 expires_at_secs: Option<u64>,
597 created_at: UnixSeconds,
598 now: UnixSeconds,
599 ) -> anyhow::Result<ProtocolDirectSendResult> {
600 let now_ms = current_unix_millis();
601 let mut options = pairwise_codec::EncodeOptions::new(created_at.get(), now_ms);
602 if let Some(expires_at_secs) = expires_at_secs {
603 options = options.with_expiration(expires_at_secs);
604 }
605 let rumor = pairwise_codec::message_event(self.owner_pubkey, text.to_string(), options)?;
606 self.send_direct_unsigned_event(peer_pubkey, chat_id, rumor, now)
607 }
608
609 pub fn send_direct_unsigned_event(
610 &mut self,
611 peer_pubkey: PublicKey,
612 chat_id: &str,
613 mut rumor: UnsignedEvent,
614 now: UnixSeconds,
615 ) -> anyhow::Result<ProtocolDirectSendResult> {
616 rumor.ensure_id();
617 let message_id = rumor
618 .id
619 .as_ref()
620 .map(ToString::to_string)
621 .unwrap_or_default();
622 let remote_payload = serde_json::to_vec(&rumor)?;
623 let sibling_payload = local_sibling_payload(peer_pubkey, &remote_payload)?;
624 self.with_state_checkpoint(|engine| {
625 let self_chat = peer_pubkey == engine.owner_pubkey;
626 let (mut event_ids, mut effects) = if self_chat {
627 (Vec::new(), Vec::new())
628 } else {
629 engine.queue_remote_payload(
630 peer_pubkey,
631 chat_id,
632 remote_payload,
633 Some(message_id.clone()),
634 &message_id,
635 now,
636 )?
637 };
638 let siblings: BTreeSet<_> = engine
639 .session_manager
640 .roster(engine.local_owner)
641 .into_iter()
642 .flat_map(|roster| roster.devices())
643 .map(|entry| entry.device_pubkey)
644 .filter(|device| *device != engine.local_device)
645 .collect();
646 let needs_sibling_delivery = self_chat && !siblings.is_empty();
647 let sibling_effects = engine.queue_local_sibling_payload(
648 chat_id,
649 sibling_payload,
650 &message_id,
651 now,
652 Some(siblings),
653 );
654 event_ids.extend(
656 sibling_effects
657 .iter()
658 .filter(|ProtocolEffect::Publish(publish)| {
659 !self_chat || publish.inner_event_id.is_some()
660 })
661 .map(|ProtocolEffect::Publish(publish)| publish.event.id.to_string()),
662 );
663 anyhow::ensure!(
664 !needs_sibling_delivery || !event_ids.is_empty(),
665 "no linked device delivery prepared"
666 );
667 effects.extend(sibling_effects);
668 engine.persist()?;
669 Ok(ProtocolDirectSendResult {
670 message_id,
671 event_ids,
672 effects,
673 })
674 })
675 }
676
677 pub fn send_direct_unsigned_event_to_peer_only(
678 &mut self,
679 peer_pubkey: PublicKey,
680 chat_id: &str,
681 mut rumor: UnsignedEvent,
682 now: UnixSeconds,
683 ) -> anyhow::Result<ProtocolDirectSendResult> {
684 rumor.ensure_id();
685 let message_id = rumor
686 .id
687 .as_ref()
688 .map(ToString::to_string)
689 .unwrap_or_default();
690 let remote_payload = serde_json::to_vec(&rumor)?;
691 self.with_state_checkpoint(|engine| {
692 engine.send_direct_remote_payload_inner(
693 peer_pubkey,
694 chat_id,
695 remote_payload,
696 Some(message_id.clone()),
697 message_id,
698 now,
699 )
700 })
701 }
702
703 pub fn send_local_sibling_unsigned_event(
704 &mut self,
705 conversation_owner: PublicKey,
706 chat_id: &str,
707 mut rumor: UnsignedEvent,
708 now: UnixSeconds,
709 ) -> anyhow::Result<ProtocolDirectSendResult> {
710 rumor.ensure_id();
711 let message_id = rumor
712 .id
713 .as_ref()
714 .map(ToString::to_string)
715 .unwrap_or_default();
716 let payload = serde_json::to_vec(&rumor)?;
717 let sibling_payload = local_sibling_payload(conversation_owner, &payload)?;
718 self.with_state_checkpoint(|engine| {
719 let effects = engine.queue_local_sibling_payload(
720 chat_id,
721 sibling_payload,
722 &message_id,
723 now,
724 None,
725 );
726 engine.persist()?;
727 Ok(ProtocolDirectSendResult {
728 message_id,
729 event_ids: effects
730 .iter()
731 .map(|effect| match effect {
732 ProtocolEffect::Publish(publish) => publish.event.id.to_string(),
733 })
734 .collect(),
735 effects,
736 })
737 })
738 }
739
740 fn queue_local_sibling_payload(
741 &mut self,
742 chat_id: &str,
743 sibling_payload: Vec<u8>,
744 message_id: &str,
745 now: UnixSeconds,
746 eligible_devices: Option<BTreeSet<NdrDevicePubkey>>,
747 ) -> Vec<ProtocolEffect> {
748 let existing = self
749 .pending_local_sibling_sends
750 .iter()
751 .position(|pending| pending.message_id == message_id && pending.chat_id == chat_id)
752 .map(|index| self.pending_local_sibling_sends.remove(index));
753 let mut pending = existing.unwrap_or_else(|| ProtocolPendingLocalSiblingSend {
754 eligible_devices,
755 chat_id: chat_id.to_string(),
756 payload: sibling_payload,
757 message_id: message_id.to_string(),
758 completed_devices: BTreeSet::new(),
759 created_at_secs: now.get(),
760 next_retry_at_secs: now.get(),
761 });
762 let sessions = self.session_manager.clone();
763 let (effects, complete) = match self
764 .prepare_pending_local_sibling_send(&mut pending, NdrUnixSeconds(now.get()))
765 {
766 Ok(result) => result,
767 Err(_) => {
768 self.session_manager = sessions;
769 (Vec::new(), false)
770 }
771 };
772 if !complete {
773 self.pending_local_sibling_sends.push(pending);
774 }
775 effects
776 }
777
778 fn send_direct_remote_payload_inner(
779 &mut self,
780 peer_pubkey: PublicKey,
781 chat_id: &str,
782 remote_payload: Vec<u8>,
783 inner_event_id: Option<String>,
784 message_id: String,
785 now: UnixSeconds,
786 ) -> anyhow::Result<ProtocolDirectSendResult> {
787 let (event_ids, effects) = self.queue_remote_payload(
788 peer_pubkey,
789 chat_id,
790 remote_payload,
791 inner_event_id,
792 &message_id,
793 now,
794 )?;
795 self.persist()?;
796 Ok(ProtocolDirectSendResult {
797 message_id,
798 event_ids,
799 effects,
800 })
801 }
802}