use super::super::{
DeRecChannelStore, DeRecEvent, DeRecSecretStore, DeRecShareStore, DeRecStateStore,
DeRecTransport, PendingAction, SecretKind, SecretValue, StateItem, StateKey,
events::UnpairAck,
};
use super::peer_endpoint;
use crate::derec_message::current_timestamp;
use crate::{
Error, Result,
primitives::unpairing::{
request::produce as produce_unpair_request,
response::{self as unpairing_response, process as process_unpair_response},
},
types::{ChannelId, SharedKey},
};
use derec_proto::{
DeRecResult, MessageBody, StatusEnum, UnpairRequestMessage, UnpairResponseMessage,
};
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0))
)]
#[allow(clippy::too_many_arguments)]
pub(in crate::protocol) async fn handle<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
St: DeRecStateStore,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
state_store: &mut St,
secret_id: u64,
channel_id: ChannelId,
inner: MessageBody,
shared_key: SharedKey,
inbound_trace_id: u64,
) -> Result<Vec<DeRecEvent>> {
match inner {
MessageBody::UnpairRequest(request) => {
on_request(channel_id, request, shared_key, inbound_trace_id)
}
MessageBody::UnpairResponse(response) => {
on_response(
channel_store,
share_store,
secret_store,
state_store,
secret_id,
channel_id,
&response,
)
.await
}
_ => Err(Error::Invariant(
"unexpected MessageBody variant in unpairing handler",
)),
}
}
#[cfg_attr(feature = "logging", tracing::instrument(skip_all))]
#[allow(clippy::too_many_arguments)]
pub(in crate::protocol) async fn start<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
T: DeRecTransport,
St: DeRecStateStore,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
transport: &T,
state_store: &mut St,
secret_id: u64,
channel_id: ChannelId,
memo: Option<String>,
unpair_ack: UnpairAck,
now: u64,
reply_to: Option<derec_proto::TransportProtocol>,
) -> Result<Vec<DeRecEvent>> {
let memo_str = memo.unwrap_or_default();
let mut events = Vec::new();
let shared_key = match secret_store
.load(secret_id, channel_id, SecretKind::SharedKey)
.await?
{
Some(SecretValue::SharedKey(k)) => k,
_ => {
return Err(crate::Error::InvalidInput(
"channel has no shared key — not yet paired",
));
}
};
let request = produce_unpair_request(channel_id, &memo_str, &shared_key, reply_to)?;
let envelope = super::apply_trace_id(request.envelope, super::fresh_trace_id())?;
let endpoint = peer_endpoint(channel_store, secret_id, channel_id).await?;
transport.send(&endpoint, envelope).await?;
match unpair_ack {
UnpairAck::NotRequired => {
drop_channel_state(
channel_store,
share_store,
secret_store,
secret_id,
channel_id,
)
.await?;
events.push(DeRecEvent::Unpaired { channel_id });
}
UnpairAck::Required => {
state_store
.save(
secret_id,
StateItem::PendingUnpair {
channel_id,
started_at: now,
},
)
.await?;
}
}
#[cfg(feature = "logging")]
tracing::debug!(
channel_id = channel_id.0,
ack = ?unpair_ack,
"unpair request sent"
);
Ok(events)
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0))
)]
#[allow(clippy::too_many_arguments)]
pub(in crate::protocol) async fn accept<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
T: DeRecTransport,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
transport: &T,
secret_id: u64,
channel_id: ChannelId,
request: &UnpairRequestMessage,
shared_key: &SharedKey,
trace_id: u64,
) -> Result<Vec<DeRecEvent>> {
let resp = unpairing_response::produce(channel_id, shared_key)?;
let envelope = super::apply_trace_id(resp.envelope, trace_id)?;
let endpoint = super::resolve_response_endpoint(
channel_store,
secret_id,
channel_id,
request.reply_to.as_ref(),
)
.await?;
transport.send(&endpoint, envelope).await?;
drop_channel_state(
channel_store,
share_store,
secret_store,
secret_id,
channel_id,
)
.await?;
#[cfg(feature = "logging")]
tracing::info!("unpair accepted; local state dropped");
Ok(vec![DeRecEvent::Unpaired { channel_id }])
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0, status = status as i32))
)]
#[allow(clippy::too_many_arguments)]
pub(in crate::protocol) async fn reject<Ch: DeRecChannelStore, T: DeRecTransport>(
channel_store: &mut Ch,
transport: &T,
secret_id: u64,
channel_id: ChannelId,
request: &UnpairRequestMessage,
shared_key: &SharedKey,
status: StatusEnum,
memo: &str,
trace_id: u64,
) -> Result<()> {
let response = UnpairResponseMessage {
result: Some(DeRecResult {
status: status as i32,
memo: memo.to_owned(),
}),
timestamp: Some(current_timestamp()),
};
super::send_channel_message(
channel_store,
transport,
secret_id,
channel_id,
MessageBody::UnpairResponse(response),
shared_key,
trace_id,
request.reply_to.as_ref(),
)
.await
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0))
)]
fn on_request(
channel_id: ChannelId,
request: UnpairRequestMessage,
shared_key: SharedKey,
trace_id: u64,
) -> Result<Vec<DeRecEvent>> {
Ok(vec![DeRecEvent::ActionRequired {
channel_id,
action: PendingAction::Unpair {
channel_id,
request,
shared_key,
trace_id,
},
}])
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0))
)]
async fn on_response<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
St: DeRecStateStore,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
state_store: &mut St,
secret_id: u64,
channel_id: ChannelId,
response: &UnpairResponseMessage,
) -> Result<Vec<DeRecEvent>> {
if !state_store
.remove(secret_id, StateKey::PendingUnpair { channel_id })
.await?
{
return Ok(vec![DeRecEvent::NoOp]);
}
match process_unpair_response(response) {
Ok(_) => {
drop_channel_state(
channel_store,
share_store,
secret_store,
secret_id,
channel_id,
)
.await?;
Ok(vec![DeRecEvent::Unpaired { channel_id }])
}
Err(Error::Unpairing(crate::primitives::unpairing::UnpairingError::NonOkStatus {
status,
memo,
})) => Ok(vec![DeRecEvent::UnpairRejected {
channel_id,
status,
memo,
}]),
Err(e) => Err(e),
}
}
pub(in crate::protocol) async fn drop_channel_state<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
secret_id: u64,
channel_id: ChannelId,
) -> Result<()> {
share_store.remove_channel(secret_id, channel_id).await?;
let _ = secret_store
.remove(secret_id, channel_id, SecretKind::SharedKey)
.await;
let _ = secret_store
.remove(secret_id, channel_id, SecretKind::PairingSecret)
.await;
let _ = secret_store
.remove(secret_id, channel_id, SecretKind::PairingContact)
.await;
let _ = channel_store.remove(secret_id, channel_id).await?;
Ok(())
}