1use crate::{
2 AuthorizedDevice, DeviceMembership, DevicePubkey, DeviceRoster, DomainError, Error, Invite,
3 InviteResponse, InviteResponseEnvelope, MessageEnvelope, OwnerPubkey, ProtocolContext, Result,
4 RosterSnapshotDecision, Session, SessionState, UnixSeconds, VerifiedAppKeysIndex,
5};
6use rand::{CryptoRng, RngCore};
7use serde::{Deserialize, Serialize};
8use std::collections::{BTreeMap, BTreeSet};
9
10const MAX_INACTIVE_SESSIONS: usize = 10;
11#[derive(Debug, Clone)]
12pub struct SessionManager {
13 local_owner_pubkey: OwnerPubkey,
14 local_device_pubkey: DevicePubkey,
15 local_device_secret_key: [u8; 32],
16 local_invite: Option<Invite>,
17 verified_peer_app_keys: VerifiedAppKeysIndex,
18 users: BTreeMap<OwnerPubkey, UserRecord>,
19}
20
21#[derive(Debug, Clone)]
22struct UserRecord {
23 owner_pubkey: OwnerPubkey,
24 roster: Option<DeviceRoster>,
25 devices: BTreeMap<DevicePubkey, DeviceRecord>,
26}
27
28#[derive(Debug, Clone)]
29struct DeviceRecord {
30 device_pubkey: DevicePubkey,
31 authorized: bool,
32 is_stale: bool,
33 stale_since: Option<UnixSeconds>,
34 claimed_owner_pubkey: Option<OwnerPubkey>,
35 public_invite: Option<Invite>,
36 invite_response_generated: bool,
37 active_session: Option<Session>,
38 inactive_sessions: Vec<Session>,
39 last_activity: Option<UnixSeconds>,
40 created_at: UnixSeconds,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44pub struct SessionManagerSnapshot {
45 pub local_owner_pubkey: OwnerPubkey,
46 pub local_device_pubkey: DevicePubkey,
47 pub local_invite: Option<Invite>,
48 #[serde(default)]
49 pub verified_peer_app_keys_events: Vec<nostr::Event>,
50 pub users: Vec<UserRecordSnapshot>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub struct UserRecordSnapshot {
55 pub owner_pubkey: OwnerPubkey,
56 pub roster: Option<DeviceRoster>,
57 pub devices: Vec<DeviceRecordSnapshot>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61pub struct DeviceRecordSnapshot {
62 pub device_pubkey: DevicePubkey,
63 pub authorized: bool,
64 pub is_stale: bool,
65 pub stale_since: Option<UnixSeconds>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub claimed_owner_pubkey: Option<OwnerPubkey>,
68 pub public_invite: Option<Invite>,
69 #[serde(default)]
70 pub invite_response_generated: bool,
71 pub active_session: Option<SessionState>,
72 pub inactive_sessions: Vec<SessionState>,
73 pub last_activity: Option<UnixSeconds>,
74 pub created_at: UnixSeconds,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct PreparedSend {
79 pub recipient_owner: OwnerPubkey,
80 pub payload: Vec<u8>,
81 pub deliveries: Vec<Delivery>,
82 pub invite_responses: Vec<InviteResponseEnvelope>,
83 pub relay_gaps: Vec<RelayGap>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Delivery {
88 pub owner_pubkey: OwnerPubkey,
89 pub device_pubkey: DevicePubkey,
90 pub envelope: MessageEnvelope,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct ProcessedInviteResponse {
95 pub owner_pubkey: OwnerPubkey,
96 pub device_pubkey: DevicePubkey,
97 pub claimed_owner_pubkey: Option<OwnerPubkey>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ReceivedMessage {
102 pub owner_pubkey: OwnerPubkey,
103 pub device_pubkey: DevicePubkey,
104 pub payload: Vec<u8>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
108pub enum RelayGap {
109 MissingRoster {
110 owner_pubkey: OwnerPubkey,
111 },
112 MissingDeviceInvite {
113 owner_pubkey: OwnerPubkey,
114 device_pubkey: DevicePubkey,
115 },
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119pub struct PruneReport {
120 pub removed_devices: Vec<(OwnerPubkey, DevicePubkey)>,
121 pub removed_users: Vec<OwnerPubkey>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
125struct TargetDevice {
126 owner_pubkey: OwnerPubkey,
127 device_pubkey: DevicePubkey,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131enum SendSessionSource {
132 Active,
133 Inactive(usize),
134}
135
136mod lifecycle;
137mod records;
138mod rosters;
139mod sending;
140
141fn apply_roster_snapshot(
142 current_roster: Option<&DeviceRoster>,
143 incoming_roster: &DeviceRoster,
144) -> (RosterSnapshotDecision, DeviceRoster) {
145 let Some(current_roster) = current_roster else {
146 return (RosterSnapshotDecision::Advanced, incoming_roster.clone());
147 };
148
149 if incoming_roster.created_at > current_roster.created_at {
150 return (RosterSnapshotDecision::Advanced, incoming_roster.clone());
151 }
152
153 if incoming_roster.created_at < current_roster.created_at {
154 return (RosterSnapshotDecision::Stale, current_roster.clone());
155 }
156
157 (
158 RosterSnapshotDecision::MergedEqualTimestamp,
159 current_roster.merge(incoming_roster),
160 )
161}
162
163fn session_priority(session: &Session) -> (u8, u32, u32) {
164 let can_send = session.can_send();
165 let can_receive = session.state.receiving_chain_key.is_some()
166 || session.state.their_current_nostr_public_key.is_some()
167 || session.state.receiving_chain_message_number > 0;
168
169 let directionality = match (can_send, can_receive) {
170 (true, true) => 3,
171 (true, false) => 2,
172 (false, true) => 1,
173 (false, false) => 0,
174 };
175
176 (
177 directionality,
178 session.state.receiving_chain_message_number,
179 session.state.sending_chain_message_number,
180 )
181}
182
183fn is_one_way_bootstrap_session(session: &Session) -> bool {
184 session.state.receiving_chain_key.is_none()
185 && session.state.their_current_nostr_public_key.is_none()
186}
187
188fn merge_created_at(current: UnixSeconds, observed: UnixSeconds) -> UnixSeconds {
189 match (current.get(), observed.get()) {
190 (0, _) => observed,
191 (_, 0) => current,
192 _ => current.min(observed),
193 }
194}