pub mod error;
pub mod events;
#[cfg(any(feature = "ffi", target_arch = "wasm32"))]
pub(crate) mod pending_action_wire;
pub mod reserved_keys;
pub mod traits;
pub mod types;
mod builder;
mod handlers;
use crate::{
Error, Result,
primitives::pairing::request::create_contact as create_contact_message,
types::ChannelId,
};
pub use builder::DeRecProtocolBuilder;
use derec_proto::{ContactMessage, ContactMode, DeRecMessage, StatusEnum, TransportProtocol};
pub use error::{
ChannelStoreError, ProcessError, SecretStoreError, ShareStoreError, StateStoreError,
};
use prost::Message;
use std::collections::{HashMap, HashSet};
pub use traits::{
ChannelStoreFuture, DeRecChannelStore, DeRecSecretStore, DeRecShareStore, DeRecStateStore,
DeRecTransport, DeRecUserSecretStore, SecretStoreFuture, ShareStoreFuture, StateStoreFuture,
TransportFuture,
};
pub use types::{
Channel, ChannelShare, ChannelStatus, HelperInfo, MissingPolicy, PairingKeyMaterial,
ReplicaInfo, ReplicaSecretPayload, Secret, SecretKind, SecretValue, Share, StateItem, StateKey,
StateKind, Target, UserSecret, UserSecrets,
};
pub use events::{
AutoAcceptPolicy, DeRecEvent, DeRecFlow, PendingAction, PendingActionKind, UnpairAck,
};
pub use handlers::restore::RestoreError;
#[cfg(not(target_arch = "wasm32"))]
use crate::utils::now_secs;
#[cfg(target_arch = "wasm32")]
use crate::wasm::now_secs;
pub struct DeRecProtocol<
ChannelStore: DeRecChannelStore,
ShareStore: DeRecShareStore,
SecretStore: DeRecSecretStore,
UserSecretStore: DeRecUserSecretStore,
StateStore: DeRecStateStore,
Transport: DeRecTransport,
> {
pub channel_store: ChannelStore,
pub share_store: ShareStore,
pub secret_store: SecretStore,
pub user_secret_store: UserSecretStore,
pub state_store: StateStore,
pub transport: Transport,
pub own_transport: TransportProtocol,
pub(crate) unpair_ack: UnpairAck,
threshold: usize,
keep_versions_count: usize,
timeout_in_secs: u64,
pub(crate) communication_info: HashMap<String, String>,
pub(crate) auto_respond_on_failure: bool,
pub(crate) auto_reply_to: bool,
pub(crate) auto_accept: AutoAcceptPolicy,
pub(crate) replica_id: Option<u64>,
pub(crate) parameter_range: Option<derec_proto::ParameterRange>,
secret_id: u64,
}
impl<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
Us: DeRecUserSecretStore,
T: DeRecTransport,
St: DeRecStateStore,
> DeRecProtocol<Ch, Sh, Ss, Us, St, T>
{
#[allow(clippy::too_many_arguments)]
pub fn new(
secret_id: u64,
channel_store: Ch,
share_store: Sh,
secret_store: Ss,
user_secret_store: Us,
state_store: St,
transport: T,
own_transport: TransportProtocol,
threshold: usize,
keep_versions_count: usize,
timeout_in_secs: u64,
) -> Result<Self> {
if threshold < 2 {
return Err(crate::Error::InvalidInput(
"threshold must be >= 2; 0 or 1 lets a single helper reconstruct the secret \
and defeats threshold sharing",
));
}
Ok(Self {
channel_store,
share_store,
secret_store,
user_secret_store,
state_store,
transport,
own_transport,
unpair_ack: UnpairAck::Required,
threshold,
keep_versions_count,
timeout_in_secs,
communication_info: HashMap::new(),
auto_respond_on_failure: false,
auto_reply_to: false,
auto_accept: AutoAcceptPolicy::default(),
replica_id: None,
parameter_range: None,
secret_id,
})
}
pub fn secret_id(&self) -> u64 {
self.secret_id
}
pub fn replica_id(&self) -> Option<u64> {
self.replica_id
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
pub async fn create_contact(
&mut self,
channel_id: Option<ChannelId>,
contact_mode: ContactMode,
nonce: Option<u64>,
) -> Result<ContactMessage> {
let channel_id = channel_id.unwrap_or_else(|| ChannelId(rand::random::<u64>()));
#[cfg(feature = "logging")]
tracing::debug!(
channel_id = channel_id.0,
contact_mode = contact_mode as i32,
"creating contact message"
);
let result = create_contact_message(
channel_id,
contact_mode,
self.own_transport.clone(),
nonce,
)?;
match result.secret_key {
Some(secret_key) => {
self.secret_store
.save(
self.secret_id,
channel_id,
SecretValue::PairingSecret(PairingKeyMaterial::from_secret(&secret_key)),
)
.await?;
}
None => {
self.secret_store
.save(
self.secret_id,
channel_id,
SecretValue::PairingContact(result.contact_message.clone()),
)
.await?;
}
}
#[cfg(feature = "logging")]
tracing::info!(channel_id = channel_id.0, "contact message created");
Ok(result.contact_message)
}
pub fn set_communication_info(&mut self, info: HashMap<String, String>) {
self.communication_info = info;
}
pub fn set_own_transport(
&mut self,
own_transport: impl crate::transport::IntoOwnTransport,
) -> crate::Result<()> {
let tp = own_transport.into_own_transport()?;
self.own_transport = tp.into();
Ok(())
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
pub async fn start(&mut self, flow: DeRecFlow) -> Result<Vec<DeRecEvent>> {
let reply_to = self.auto_reply_to.then(|| self.own_transport.clone());
match flow {
DeRecFlow::Pairing {
kind,
contact,
peer_communication_info,
} => {
self.start_pairing(kind, contact, peer_communication_info)
.await
}
DeRecFlow::Discovery { target } => self.start_discovery(target, reply_to).await,
DeRecFlow::ProtectSecret {
secrets,
description,
} => {
self.start_protect_secret(secrets, description, reply_to)
.await
}
DeRecFlow::VerifyShares {
secret_id,
version,
target,
} => {
self.start_verify_shares(secret_id, version, target, reply_to)
.await
}
DeRecFlow::RecoverSecret { secret_id, version } => {
self.start_recover_secret(secret_id, version, reply_to)
.await
}
DeRecFlow::Unpair { channel_id, memo } => {
self.start_unpair(channel_id, memo, reply_to).await
}
DeRecFlow::UpdateChannelInfo {
target,
communication_info,
transport_protocol,
} => {
self.start_update_channel_info(target, communication_info, transport_protocol)
.await
}
}
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
pub async fn accept(&mut self, action: PendingAction) -> Result<Vec<DeRecEvent>> {
let mut events = self.accept_inner(action).await?;
let auto_publish_events = self.maybe_auto_publish_after_pair(&events).await?;
events.extend(auto_publish_events);
Ok(events)
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
pub async fn reject(
&mut self,
action: PendingAction,
status: StatusEnum,
memo: &str,
) -> Result<()> {
match action {
PendingAction::Pairing {
channel_id,
request,
trace_id,
..
} => {
handlers::pairing::reject(
&mut self.secret_store,
&self.transport,
&self.communication_info,
self.secret_id,
channel_id,
&request,
status,
memo,
trace_id,
)
.await
}
PendingAction::StoreShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::sharing::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::VerifyShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::verification::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::Discovery {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::discovery::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::GetShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::recovery::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::Unpair {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::unpairing::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::UpdateChannelInfo {
channel_id,
shared_key,
trace_id,
..
} => {
handlers::update_channel_info::reject(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&shared_key,
status,
memo,
trace_id,
)
.await
}
PendingAction::PrePair {
channel_id,
request,
trace_id,
} => {
handlers::pairing::reject_pre_pair(
&self.transport,
channel_id,
&request,
status,
memo,
trace_id,
)
.await
}
}
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(message_len = message.len()))
)]
pub async fn process(
&mut self,
message: &[u8],
) -> std::result::Result<Vec<DeRecEvent>, ProcessError> {
let _ = self.cleanup_expired_channels().await;
let mut timeout_events = self.check_sharing_round_timeouts().await;
let mut unpair_timeout_events = self.check_unpair_timeouts().await;
let envelope = DeRecMessage::decode(message).map_err(|e| ProcessError {
channel_id: None,
source: Error::ProtobufDecode(e),
})?;
let channel_id = ChannelId(envelope.channel_id);
let result = self.process_inner(&envelope, channel_id).await;
let mut events = result.map_err(|source| ProcessError {
channel_id: Some(channel_id),
source,
})?;
events = self
.apply_auto_accept(events)
.await
.map_err(|source| ProcessError {
channel_id: Some(channel_id),
source,
})?;
timeout_events.append(&mut unpair_timeout_events);
timeout_events.append(&mut events);
let mut events = timeout_events;
self.update_sharing_round(&mut events).await;
let auto_publish_events = self
.maybe_auto_publish_after_pair(&events)
.await
.map_err(|source| ProcessError {
channel_id: Some(channel_id),
source,
})?;
events.extend(auto_publish_events);
Ok(events)
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all, fields(channel_id = channel_id.0)))]
pub async fn get_fingerprint(&self, channel_id: ChannelId) -> Result<String> {
let shared_key = match self
.secret_store
.load(self.secret_id, channel_id, SecretKind::SharedKey)
.await?
{
Some(SecretValue::SharedKey(key)) => key,
_ => {
return Err(Error::InvalidInput(
"channel has no shared key — not yet paired",
));
}
};
Ok(derec_cryptography::replica::fingerprint(&shared_key))
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all, fields(channel_id = channel_id.0)))]
pub async fn verify_fingerprint(
&mut self,
channel_id: ChannelId,
fingerprint: &str,
) -> Result<bool> {
let local = self.get_fingerprint(channel_id).await?;
if local != fingerprint {
return Ok(false);
}
let mut transitioned_replica = false;
if let Some(mut channel) = self.channel_store.load(self.secret_id, channel_id).await? {
transitioned_replica = channel.role == derec_proto::SenderKind::ReplicaSource
&& channel.status == crate::protocol::types::ChannelStatus::Pending;
channel.status = crate::protocol::types::ChannelStatus::Paired;
self.channel_store.save(self.secret_id, channel).await?;
}
if transitioned_replica {
let snapshot = self.user_secret_store.load_latest(self.secret_id).await?;
let (secrets, description) = match snapshot {
Some(s) => (s.secrets, s.description),
None => (Vec::new(), None),
};
let reply_to = self.auto_reply_to.then(|| self.own_transport.clone());
self.publish_secret(secrets, description, reply_to).await?;
}
Ok(true)
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
pub async fn restore(
&mut self,
secret: &crate::protocol::types::Secret,
recovered_version: u32,
) -> Result<Vec<DeRecEvent>> {
handlers::restore::restore(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
&mut self.user_secret_store,
&self.transport,
&mut self.state_store,
&mut self.replica_id,
self.secret_id,
secret,
recovered_version,
)
.await
}
async fn start_pairing(
&mut self,
kind: derec_proto::SenderKind,
contact: derec_proto::ContactMessage,
peer_communication_info: HashMap<String, String>,
) -> Result<Vec<DeRecEvent>> {
let channel_id = handlers::pairing::start(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
&self.own_transport,
&self.communication_info,
self.secret_id,
kind,
contact,
peer_communication_info,
self.replica_id,
self.parameter_range,
)
.await?;
Ok(vec![DeRecEvent::PairingStarted {
channel_id: ChannelId(channel_id),
kind,
}])
}
async fn start_discovery(
&mut self,
target: crate::protocol::types::Target,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
let resolved =
handlers::resolve_target(&mut self.channel_store, self.secret_id, target.clone())
.await?;
handlers::require_role(
&self.channel_store,
self.secret_id,
&resolved,
derec_proto::SenderKind::Owner,
)
.await?;
handlers::discovery::start(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
self.secret_id,
target,
reply_to,
)
.await
}
async fn start_protect_secret(
&mut self,
secrets: Vec<crate::protocol::types::UserSecret>,
description: Option<String>,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
self.publish_secret(secrets, description, reply_to).await
}
async fn publish_secret(
&mut self,
secrets: Vec<crate::protocol::types::UserSecret>,
description: Option<String>,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
let Some(round) = handlers::sharing::start(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
&mut self.user_secret_store,
&self.transport,
secrets,
description,
self.threshold,
self.keep_versions_count,
self.secret_id,
reply_to,
self.replica_id,
)
.await?
else {
return Ok(Vec::new());
};
let version = round.version;
let pending: HashSet<ChannelId> = round
.outcomes
.iter()
.filter_map(|(cid, r)| r.as_ref().ok().map(|_| *cid))
.collect();
if !pending.is_empty() {
self.state_store
.save(
self.secret_id,
StateItem::SharingRound {
version,
pending,
confirmed: HashSet::new(),
failed: HashSet::new(),
started_at: now_secs(),
},
)
.await?;
}
Ok(round
.outcomes
.into_iter()
.map(|(channel_id, res)| match res {
Ok(()) => DeRecEvent::ProtectSecretStarted {
channel_id,
version,
},
Err(e) => DeRecEvent::ProtectSecretFailed {
channel_id,
version,
error: e.to_string(),
},
})
.collect())
}
async fn start_verify_shares(
&mut self,
secret_id: u64,
version: u32,
target: crate::protocol::types::Target,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
let resolved =
handlers::resolve_target(&mut self.channel_store, self.secret_id, target.clone())
.await?;
handlers::require_role(
&self.channel_store,
self.secret_id,
&resolved,
derec_proto::SenderKind::Owner,
)
.await?;
handlers::verification::start(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
&mut self.state_store,
version,
target,
secret_id,
reply_to,
)
.await
}
async fn start_recover_secret(
&mut self,
secret_id: u64,
version: u32,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
let all_paired: Vec<crate::types::ChannelId> = self
.channel_store
.channels(self.secret_id)
.await?
.iter()
.map(|c| c.id)
.collect();
handlers::require_role(
&self.channel_store,
self.secret_id,
&all_paired,
derec_proto::SenderKind::Owner,
)
.await?;
handlers::recovery::start(
&mut self.channel_store,
&mut self.secret_store,
&mut self.state_store,
&self.transport,
secret_id,
version,
reply_to,
)
.await
}
async fn start_unpair(
&mut self,
channel_id: ChannelId,
memo: Option<String>,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
handlers::require_role(
&self.channel_store,
self.secret_id,
&[channel_id],
derec_proto::SenderKind::Owner,
)
.await?;
let mut events = vec![DeRecEvent::UnpairStarted { channel_id }];
events.extend(
handlers::unpairing::start(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
&self.transport,
&mut self.state_store,
self.secret_id,
channel_id,
memo,
self.unpair_ack,
now_secs(),
reply_to,
)
.await?,
);
Ok(events)
}
async fn start_update_channel_info(
&mut self,
target: crate::protocol::types::Target,
communication_info: Option<HashMap<String, String>>,
transport_protocol: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
handlers::update_channel_info::start(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
self.secret_id,
target,
communication_info,
transport_protocol,
)
.await
}
async fn accept_inner(&mut self, action: PendingAction) -> Result<Vec<DeRecEvent>> {
match action {
PendingAction::Pairing {
channel_id,
request,
kind,
trace_id,
..
} => {
handlers::pairing::accept(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
&self.communication_info,
self.secret_id,
channel_id,
&request,
kind,
trace_id,
self.replica_id,
self.parameter_range,
)
.await
}
PendingAction::StoreShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::sharing::accept(
&mut self.channel_store,
&mut self.share_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::VerifyShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::verification::accept(
&mut self.channel_store,
&mut self.share_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::Discovery {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::discovery::accept(
&mut self.channel_store,
&mut self.share_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::GetShare {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::recovery::accept(
&mut self.channel_store,
&mut self.share_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::Unpair {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::unpairing::accept(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::UpdateChannelInfo {
channel_id,
request,
shared_key,
trace_id,
} => {
handlers::update_channel_info::accept(
&mut self.channel_store,
&self.transport,
self.secret_id,
channel_id,
&request,
&shared_key,
trace_id,
)
.await
}
PendingAction::PrePair {
channel_id,
request,
trace_id,
} => {
handlers::pairing::accept_pre_pair(
&mut self.secret_store,
&self.transport,
self.secret_id,
channel_id,
&request,
trace_id,
)
.await
}
}
}
async fn maybe_auto_publish_after_pair(
&mut self,
events: &[DeRecEvent],
) -> Result<Vec<DeRecEvent>> {
let helper_just_paired = events.iter().any(|e| {
matches!(
e,
DeRecEvent::PairingCompleted {
kind: derec_proto::SenderKind::Owner,
..
}
)
});
if !helper_just_paired {
return Ok(Vec::new());
}
let snapshot = self.user_secret_store.load_latest(self.secret_id).await?;
let (secrets, description) = match snapshot {
Some(s) => (s.secrets, s.description),
None => {
if !self.has_paired_replica_destination().await? {
return Ok(Vec::new());
}
(Vec::new(), None)
}
};
let reply_to = self.auto_reply_to.then(|| self.own_transport.clone());
self.publish_secret(secrets, description, reply_to).await
}
async fn has_paired_replica_destination(&self) -> Result<bool> {
let channels = self.channel_store.channels(self.secret_id).await?;
Ok(channels.iter().any(|c| {
c.role == derec_proto::SenderKind::ReplicaSource
&& c.status == crate::protocol::types::ChannelStatus::Paired
}))
}
async fn apply_auto_accept(&mut self, events: Vec<DeRecEvent>) -> Result<Vec<DeRecEvent>> {
let mut out = Vec::with_capacity(events.len());
for event in events {
match event {
DeRecEvent::ActionRequired { channel_id, action }
if self.auto_accept.allows(&action) =>
{
let action_kind = action.kind();
out.push(DeRecEvent::AutoAccepted {
channel_id,
action_kind,
});
let accept_events = self.accept_inner(action).await?;
out.extend(accept_events);
}
other => out.push(other),
}
}
Ok(out)
}
async fn process_inner(
&mut self,
message: &DeRecMessage,
channel_id: ChannelId,
) -> Result<Vec<DeRecEvent>> {
if self.is_message_expired(message, channel_id) {
return Ok(vec![DeRecEvent::NoOp]);
}
if let Some(events) = self.process_channel_message(message, channel_id).await? {
return Ok(events);
}
if let Some(events) = self.process_pairing_message(message, channel_id).await? {
return Ok(events);
}
#[cfg(feature = "logging")]
tracing::warn!(channel_id = channel_id.0, "no key material for channel");
Err(Error::InvalidInput(
"unknown channel_id: no shared key or pairing secret found",
))
}
fn is_message_expired(
&self,
envelope: &DeRecMessage,
#[cfg_attr(not(feature = "logging"), allow(unused))] channel_id: ChannelId,
) -> bool {
let Some(ts) = &envelope.timestamp else {
return false;
};
let msg_secs = ts.seconds as u64;
let now = now_secs();
let age = now.saturating_sub(msg_secs);
if age > self.timeout_in_secs {
#[cfg(feature = "logging")]
tracing::warn!(
channel_id = channel_id.0,
message_age_secs = age,
timeout_secs = self.timeout_in_secs,
"message discarded — older than configured timeout"
);
return true;
}
false
}
async fn process_channel_message(
&mut self,
message: &DeRecMessage,
channel_id: ChannelId,
) -> Result<Option<Vec<DeRecEvent>>> {
let Some(SecretValue::SharedKey(shared_key)) = self
.secret_store
.load(self.secret_id, channel_id, SecretKind::SharedKey)
.await?
else {
return Ok(None);
};
if let Some(channel) = self.channel_store.load(self.secret_id, channel_id).await? {
if channel.status == crate::protocol::types::ChannelStatus::Pending {
#[cfg(feature = "logging")]
tracing::warn!(
channel_id = channel_id.0,
"message ignored — channel is pending fingerprint verification"
);
return Ok(Some(vec![DeRecEvent::NoOp]));
}
}
let events = handlers::handle(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
&self.transport,
&mut self.state_store,
message,
self.secret_id,
channel_id,
&shared_key,
)
.await?;
Ok(Some(events))
}
async fn process_pairing_message(
&mut self,
message: &DeRecMessage,
channel_id: ChannelId,
) -> Result<Option<Vec<DeRecEvent>>> {
use derec_proto::MessageBody;
if let Ok(inner) = crate::derec_message::extract_inner_plaintext_message(&message.message) {
match inner {
inner @ MessageBody::PrePairRequest(_) => {
let has_pairing_secret = matches!(
self.secret_store
.load(self.secret_id, channel_id, SecretKind::PairingSecret)
.await?,
Some(SecretValue::PairingSecret(_))
);
let has_pairing_contact = matches!(
self.secret_store
.load(self.secret_id, channel_id, SecretKind::PairingContact)
.await?,
Some(SecretValue::PairingContact(_))
);
if !has_pairing_secret && !has_pairing_contact {
return Ok(None);
}
let events = handlers::pairing::handle_pre_pair_request(&inner, channel_id, message.trace_id)?;
return Ok(Some(events));
}
MessageBody::PrePairResponse(resp) => {
let Some(SecretValue::PairingContact(contact)) = self
.secret_store
.load(self.secret_id, channel_id, SecretKind::PairingContact)
.await?
else {
return Ok(None);
};
let events = handlers::pairing::on_pre_pair_response(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
&self.own_transport,
&self.communication_info,
self.secret_id,
channel_id,
&contact,
&resp,
self.replica_id,
self.parameter_range,
)
.await?;
return Ok(Some(events));
}
_ => {} }
}
let Some(SecretValue::PairingSecret(pairing_secret)) = self
.secret_store
.load(self.secret_id, channel_id, SecretKind::PairingSecret)
.await?
else {
return Ok(None);
};
let pairing_secret = pairing_secret.to_secret()?;
let events = handlers::handle_pairing(
&mut self.channel_store,
&mut self.secret_store,
&self.transport,
&self.communication_info,
message,
self.secret_id,
channel_id,
&pairing_secret,
self.replica_id,
self.parameter_range.as_ref(),
)
.await?;
Ok(Some(events))
}
async fn check_sharing_round_timeouts(&mut self) -> Vec<DeRecEvent> {
let Ok(Some(StateItem::SharingRound {
version,
mut pending,
confirmed,
mut failed,
started_at,
})) = self
.state_store
.load(self.secret_id, StateKey::SharingRound)
.await
else {
return vec![];
};
let now = now_secs();
if now.saturating_sub(started_at) <= self.timeout_in_secs {
return vec![];
}
let timed_out: Vec<ChannelId> = pending.drain().collect();
let mut events = Vec::with_capacity(timed_out.len());
for channel_id in timed_out {
failed.insert(channel_id);
events.push(DeRecEvent::ShareRejected {
channel_id,
version,
status: StatusEnum::Fail as i32,
memo: "timeout".to_owned(),
});
#[cfg(feature = "logging")]
tracing::warn!(
channel_id = channel_id.0,
version,
"sharing round: helper timed out"
);
}
let _ = self
.state_store
.save(
self.secret_id,
StateItem::SharingRound {
version,
pending,
confirmed,
failed,
started_at,
},
)
.await;
events
}
async fn check_unpair_timeouts(&mut self) -> Vec<DeRecEvent> {
let now = now_secs();
let all = match self
.state_store
.load_all(self.secret_id, StateKind::PendingUnpair)
.await
{
Ok(items) => items,
Err(_) => return Vec::new(),
};
let expired: Vec<ChannelId> = all
.into_iter()
.filter_map(|item| match item {
StateItem::PendingUnpair {
channel_id,
started_at,
} if now.saturating_sub(started_at) > self.timeout_in_secs => Some(channel_id),
_ => None,
})
.collect();
let mut events = Vec::with_capacity(expired.len());
for cid in expired {
let _ = self
.state_store
.remove(self.secret_id, StateKey::PendingUnpair { channel_id: cid })
.await;
if handlers::unpairing::drop_channel_state(
&mut self.channel_store,
&mut self.share_store,
&mut self.secret_store,
self.secret_id,
cid,
)
.await
.is_ok()
{
events.push(DeRecEvent::Unpaired { channel_id: cid });
}
}
events
}
async fn update_sharing_round(&mut self, events: &mut Vec<DeRecEvent>) {
let Ok(Some(StateItem::SharingRound {
version: round_version,
mut pending,
mut confirmed,
mut failed,
started_at,
})) = self
.state_store
.load(self.secret_id, StateKey::SharingRound)
.await
else {
return;
};
for event in events.iter() {
match event {
DeRecEvent::ShareConfirmed {
channel_id,
version,
} if *version == round_version => {
pending.remove(channel_id);
confirmed.insert(*channel_id);
}
DeRecEvent::ShareRejected {
channel_id,
version,
..
} if *version == round_version => {
pending.remove(channel_id);
failed.insert(*channel_id);
}
_ => {}
}
}
let is_complete = pending.is_empty();
let confirmed_count = confirmed.len();
let failed_count = failed.len();
if is_complete {
let threshold_met = confirmed_count >= self.threshold;
let _ = self
.state_store
.remove(self.secret_id, StateKey::SharingRound)
.await;
events.push(DeRecEvent::SharingComplete {
version: round_version,
confirmed_count,
failed_count,
threshold_met,
});
#[cfg(feature = "logging")]
tracing::info!(
version = round_version,
confirmed_count,
failed_count,
threshold_met,
"sharing round complete"
);
} else {
let _ = self
.state_store
.save(
self.secret_id,
StateItem::SharingRound {
version: round_version,
pending,
confirmed,
failed,
started_at,
},
)
.await;
}
}
async fn cleanup_expired_channels(&mut self) -> Result<Vec<ChannelId>> {
let now = now_secs();
let timeout = self.timeout_in_secs;
let channels = self.channel_store.channels(self.secret_id).await?;
let mut removed = Vec::new();
for channel in channels {
if channel.status == crate::protocol::types::ChannelStatus::Pending
&& now.saturating_sub(channel.created_at) > timeout
{
self.channel_store
.remove(self.secret_id, channel.id)
.await?;
let _ = self
.secret_store
.remove(self.secret_id, channel.id, SecretKind::PairingSecret)
.await;
let _ = self
.secret_store
.remove(self.secret_id, channel.id, SecretKind::PairingContact)
.await;
#[cfg(feature = "logging")]
tracing::info!(
channel_id = channel.id.0,
elapsed_secs = now.saturating_sub(channel.created_at),
"expired pending channel removed"
);
removed.push(channel.id);
}
}
Ok(removed)
}
}