const PROTOCOL_ENGINE_STATE_KEY: &str = "appcore/protocol-engine-state-v1";
const PROTOCOL_ENGINE_STATE_VERSION: u32 = 1;
const LOCAL_SIBLING_PROTOCOL: &str = "ndr-local-sibling-copy";
const PENDING_RETRY_DELAY_SECS: u64 = 2;
const LOCAL_SIBLING_ROSTER_PROBE_TTL_SECS: u64 = 120;
fn default_true() -> bool {
true
}
#[derive(Debug, Serialize, Deserialize)]
struct ProtocolEnginePersistedState {
version: u32,
session_manager: SessionManagerSnapshot,
group_manager: GroupManagerSnapshot,
#[serde(default)]
latest_app_keys_created_at: BTreeMap<String, u64>,
#[serde(default)]
pending_outbound: Vec<ProtocolPendingOutbound>,
#[serde(default)]
pending_inbound: Vec<ProtocolPendingInbound>,
#[serde(default)]
pending_group_fanouts: Vec<ProtocolPendingGroupFanout>,
#[serde(default)]
pending_group_pairwise_payloads: Vec<ProtocolPendingGroupPairwisePayload>,
#[serde(default)]
pending_group_sender_key_messages:
Vec<nostr_double_ratchet_nostr::nostr_codec::ParsedGroupSenderKeyMessageEvent>,
#[serde(default)]
pending_group_sender_key_repairs: Vec<ProtocolPendingGroupSenderKeyRepair>,
#[serde(default)]
pending_decrypted_deliveries: Vec<ProtocolPendingDecryptedDelivery>,
#[serde(default)]
subscription_generation: u64,
#[serde(default)]
last_backfill_attempt_secs: u64,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct ProtocolPublishEvent {
pub event: Event,
pub inner_event_id: Option<String>,
pub target_owner_pubkey_hex: Option<String>,
pub target_device_id: Option<String>,
}
#[derive(Clone, Debug)]
pub enum ProtocolEffect {
PublishSigned(Event),
PublishSignedForInnerEvent {
event: Event,
inner_event_id: Option<String>,
target_owner_pubkey_hex: Option<String>,
target_device_id: Option<String>,
},
PublishStagedFirstContact {
bootstrap: Vec<ProtocolPublishEvent>,
payload: Vec<ProtocolPublishEvent>,
},
FetchProtocolState {
filters: Vec<Filter>,
reason: &'static str,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProtocolPendingOutbound {
pub message_id: String,
pub chat_id: String,
recipient_owner_hex: String,
#[serde(default = "default_true")]
send_remote: bool,
remote_payload: Vec<u8>,
local_sibling_payload: Option<Vec<u8>>,
inner_event_id: Option<String>,
#[serde(default)]
delivered_remote_device_hexes: Vec<String>,
#[serde(default)]
delivered_local_device_hexes: Vec<String>,
#[serde(default)]
probe_local_sibling_roster: bool,
created_at_secs: u64,
next_retry_at_secs: u64,
reason: ProtocolPendingReason,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProtocolPendingReason {
MissingRoster,
MissingDeviceInvite,
PublishRetry,
}
impl ProtocolPendingOutbound {
fn waits_for_remote_protocol_state(&self) -> bool {
matches!(
self.reason,
ProtocolPendingReason::MissingRoster | ProtocolPendingReason::MissingDeviceInvite
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct ProtocolPendingInbound {
event: Event,
created_at_secs: u64,
next_retry_at_secs: u64,
#[serde(default)]
event_id: String,
#[serde(default)]
envelope: Option<MessageEnvelope>,
#[serde(default)]
sender_message_pubkey_hex: Option<String>,
#[serde(default)]
resolved_owner_pubkey_hex: Option<String>,
#[serde(default)]
claimed_owner_pubkey_hex: Option<String>,
#[serde(default)]
metadata_verified: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProtocolPendingInboundTestDebug {
pub event_id: String,
pub sender_message_pubkey_hex: Option<String>,
pub claimed_owner_pubkey_hex: Option<String>,
pub has_envelope: bool,
pub metadata_verified: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct ProtocolPendingGroupFanout {
group_id: String,
fanout: GroupPendingFanout,
inner_event_id: Option<String>,
created_at_secs: u64,
next_retry_at_secs: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct ProtocolPendingGroupPairwisePayload {
sender_owner: NdrOwnerPubkey,
sender_device: Option<NdrDevicePubkey>,
payload: Vec<u8>,
created_at_secs: u64,
next_retry_at_secs: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct ProtocolPendingGroupSenderKeyRepair {
group_id: String,
sender_event_pubkey_hex: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
key_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
message_number: Option<u32>,
#[serde(default)]
required_revision: Option<u64>,
created_at_secs: u64,
last_requested_at_secs: u64,
request_count: u32,
next_retry_at_secs: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct ProtocolPendingDecryptedDelivery {
sender: PublicKey,
sender_device: Option<PublicKey>,
conversation_owner: Option<PublicKey>,
content: String,
event_id: Option<String>,
created_at_secs: u64,
}
#[derive(Clone, Debug)]
struct KnownMessageAuthorCache {
pubkeys: Vec<PublicKey>,
pubkey_set: HashSet<PublicKey>,
hexes: HashSet<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ProtocolDirectSendResult {
pub message_id: String,
pub event_ids: Vec<String>,
pub effects: Vec<ProtocolEffect>,
pub queued_targets: Vec<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ProtocolRetryResult {
pub message_id: String,
pub chat_id: String,
pub event_ids: Vec<String>,
pub effects: Vec<ProtocolEffect>,
pub queued_targets: Vec<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ProtocolGroupSendResult {
pub snapshot: Option<GroupSnapshot>,
pub message_id: Option<String>,
pub event_ids: Vec<String>,
pub effects: Vec<ProtocolEffect>,
pub queued_targets: Vec<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ProtocolGroupIncomingResult {
pub events: Vec<GroupIncomingEvent>,
pub effects: Vec<ProtocolEffect>,
pub queued_targets: Vec<String>,
pub consumed: bool,
pub pending: bool,
}
#[derive(Clone, Debug, Default)]
pub struct ProtocolRetryBatch {
pub direct_results: Vec<ProtocolRetryResult>,
pub group_result: ProtocolGroupIncomingResult,
pub direct_messages: Vec<ProtocolDecryptedMessage>,
pub effects: Vec<ProtocolEffect>,
}
impl ProtocolRetryBatch {
pub fn is_empty(&self) -> bool {
self.direct_results.is_empty()
&& self.group_result.events.is_empty()
&& self.group_result.effects.is_empty()
&& self.group_result.queued_targets.is_empty()
&& self.direct_messages.is_empty()
&& self.effects.is_empty()
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct ProtocolAcceptInviteResult {
pub owner_pubkey: PublicKey,
pub inviter_device_pubkey: PublicKey,
pub device_id: String,
pub effects: Vec<ProtocolEffect>,
}
#[derive(Clone, Debug)]
pub struct ProtocolDecryptedMessage {
pub sender: PublicKey,
pub sender_device: Option<PublicKey>,
pub conversation_owner: Option<PublicKey>,
pub content: String,
pub event_id: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProtocolDeviceOwnerHint {
pub owner: PublicKey,
pub verified: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ProtocolSenderOwnerResolution {
Verified {
owner: NdrOwnerPubkey,
},
PendingOwnerClaim {
storage_owner: NdrOwnerPubkey,
claimed_owner: NdrOwnerPubkey,
sender_device: NdrDevicePubkey,
},
ProvisionalDeviceOwner {
owner: NdrOwnerPubkey,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ProtocolSenderDeviceRecord {
storage_owner: NdrOwnerPubkey,
device_pubkey: NdrDevicePubkey,
claimed_owner_pubkey: Option<NdrOwnerPubkey>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct ProtocolPendingInboundMetadata {
event_id: String,
envelope: Option<MessageEnvelope>,
sender_message_pubkey_hex: Option<String>,
resolved_owner_pubkey_hex: Option<String>,
claimed_owner_pubkey_hex: Option<String>,
metadata_verified: bool,
}
impl From<ProtocolPendingDecryptedDelivery> for ProtocolDecryptedMessage {
fn from(pending: ProtocolPendingDecryptedDelivery) -> Self {
Self {
sender: pending.sender,
sender_device: pending.sender_device,
conversation_owner: pending.conversation_owner,
content: pending.content,
event_id: pending.event_id,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProtocolEngineDebugSnapshot {
pub known_message_author_count: usize,
pub pending_outbound_count: usize,
pub pending_inbound_count: usize,
pub pending_group_fanout_count: usize,
pub pending_group_pairwise_payload_count: usize,
pub pending_group_sender_key_message_count: usize,
pub pending_group_sender_key_repair_count: usize,
pub pending_group_sender_key_repair_last_requested_at_secs: u64,
pub pending_outbound_targets: Vec<String>,
#[serde(default)]
pub pending_outbound_details: Vec<ProtocolPendingOutboundDebug>,
#[serde(default)]
pub pending_group_fanout_targets: Vec<String>,
pub subscription_generation: u64,
pub last_backfill_attempt_secs: u64,
pub latest_app_keys_owner_count: usize,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProtocolPendingOutboundDebug {
pub message_id: String,
pub chat_id: String,
pub recipient_owner_hex: String,
pub reason: String,
pub probe_local_sibling_roster: bool,
pub delivered_remote_device_hexes: Vec<String>,
pub delivered_local_device_hexes: Vec<String>,
pub remaining_remote_targets: Vec<String>,
pub remaining_local_sibling_targets: Vec<String>,
pub queued_targets: Vec<String>,
pub next_retry_at_secs: u64,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct ProtocolMessageSessionDebugSnapshot {
pub state: SessionState,
pub tracked_sender_pubkeys: Vec<PublicKey>,
pub has_receiving_capability: bool,
}
pub struct ProtocolEngine {
owner_pubkey: PublicKey,
local_owner: NdrOwnerPubkey,
local_device: NdrDevicePubkey,
storage: Arc<dyn StorageAdapter>,
session_manager: SessionManager,
group_manager: NostrGroupManager,
latest_app_keys_created_at: BTreeMap<String, u64>,
pending_outbound: Vec<ProtocolPendingOutbound>,
pending_inbound: Vec<ProtocolPendingInbound>,
pending_group_fanouts: Vec<ProtocolPendingGroupFanout>,
pending_group_pairwise_payloads: Vec<ProtocolPendingGroupPairwisePayload>,
pending_group_sender_key_messages:
Vec<nostr_double_ratchet_nostr::nostr_codec::ParsedGroupSenderKeyMessageEvent>,
pending_group_sender_key_repairs: Vec<ProtocolPendingGroupSenderKeyRepair>,
pending_decrypted_deliveries: Vec<ProtocolPendingDecryptedDelivery>,
known_message_author_cache: std::cell::RefCell<Option<KnownMessageAuthorCache>>,
known_message_author_cache_build_count: std::cell::Cell<u64>,
subscription_generation: u64,
last_backfill_attempt_secs: u64,
pub batch_depth: std::cell::Cell<u32>,
pub batch_persist_dirty: std::cell::Cell<bool>,
}
#[derive(Clone)]
struct ProtocolEngineCheckpoint {
session_manager: SessionManager,
group_manager: NostrGroupManager,
latest_app_keys_created_at: BTreeMap<String, u64>,
pending_outbound: Vec<ProtocolPendingOutbound>,
pending_inbound: Vec<ProtocolPendingInbound>,
pending_group_fanouts: Vec<ProtocolPendingGroupFanout>,
pending_group_pairwise_payloads: Vec<ProtocolPendingGroupPairwisePayload>,
pending_group_sender_key_messages:
Vec<nostr_double_ratchet_nostr::nostr_codec::ParsedGroupSenderKeyMessageEvent>,
pending_group_sender_key_repairs: Vec<ProtocolPendingGroupSenderKeyRepair>,
pending_decrypted_deliveries: Vec<ProtocolPendingDecryptedDelivery>,
subscription_generation: u64,
last_backfill_attempt_secs: u64,
}