Skip to main content

nostr_double_ratchet/
session_manager.rs

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}