pub(super) mod discovery;
pub(super) mod pairing;
pub(super) mod recovery;
pub(super) mod restore;
pub(super) mod sharing;
pub(super) mod unpairing;
pub(super) mod update_channel_info;
pub(super) mod verification;
use super::{
DeRecChannelStore, DeRecEvent, DeRecSecretStore, DeRecShareStore, DeRecStateStore,
DeRecTransport,
};
use crate::{
Error, Result,
derec_message::{DeRecMessageBuilder, current_timestamp},
protocol::types::Target,
types::{ChannelId, SharedKey},
};
use derec_cryptography::pairing::PairingSecretKeyMaterial;
use derec_proto::{DeRecMessage, MessageBody, SenderKind, TransportProtocol};
use prost::Message;
use std::collections::HashMap;
pub(super) async fn require_role<Ch: DeRecChannelStore>(
channel_store: &Ch,
secret_id: u64,
channel_ids: &[ChannelId],
expected: SenderKind,
) -> Result<()> {
for channel_id in channel_ids {
let channel = channel_store
.load(secret_id, *channel_id)
.await?
.ok_or(Error::InvalidInput(
"channel id not present in channel store",
))?;
if channel.role != expected {
return Err(Error::RoleMismatch {
channel_id: *channel_id,
expected,
actual: channel.role,
});
}
}
Ok(())
}
pub(super) async fn resolve_target<Ch: DeRecChannelStore>(
channel_store: &mut Ch,
secret_id: u64,
target: Target,
) -> Result<Vec<ChannelId>> {
let all_channels = channel_store.channels(secret_id).await?;
let all_channel_ids: Vec<ChannelId> = all_channels.iter().map(|c| c.id).collect();
Ok(match target {
Target::All => all_channel_ids,
Target::Single(id) => {
if all_channel_ids.contains(&id) {
vec![id]
} else {
vec![]
}
}
Target::Many(ids) => ids
.into_iter()
.filter(|id| all_channel_ids.contains(id))
.collect(),
})
}
pub(super) async fn peer_endpoint<Ch: DeRecChannelStore>(
channel_store: &mut Ch,
secret_id: u64,
channel_id: ChannelId,
) -> Result<TransportProtocol> {
let channel = channel_store.load(secret_id, channel_id).await?;
channel
.map(|ch| ch.transport)
.ok_or(Error::InvalidInput("no transport endpoint for channel"))
}
pub(super) async fn resolve_response_endpoint<Ch: DeRecChannelStore>(
channel_store: &mut Ch,
secret_id: u64,
channel_id: ChannelId,
reply_to: Option<&TransportProtocol>,
) -> Result<TransportProtocol> {
if let Some(endpoint) = reply_to {
return Ok(endpoint.clone());
}
peer_endpoint(channel_store, secret_id, channel_id).await
}
pub(super) fn fresh_trace_id() -> u64 {
use rand::Rng as _;
rand::rng().next_u64()
}
pub(super) fn apply_trace_id(envelope_bytes: Vec<u8>, trace_id: u64) -> Result<Vec<u8>> {
crate::derec_message::apply_trace_id(&envelope_bytes, trace_id)
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn send_channel_message<Ch: DeRecChannelStore, T: DeRecTransport>(
channel_store: &mut Ch,
transport: &T,
secret_id: u64,
channel_id: ChannelId,
body: MessageBody,
shared_key: &SharedKey,
inbound_trace_id: u64,
reply_to: Option<&TransportProtocol>,
) -> Result<()> {
let envelope = DeRecMessageBuilder::channel()
.channel_id(channel_id)
.timestamp(current_timestamp())
.message_body(body)
.trace_id(inbound_trace_id)
.encrypt(shared_key)?
.build()?;
let wire_bytes = envelope.encode_to_vec();
let endpoint =
resolve_response_endpoint(channel_store, secret_id, channel_id, reply_to).await?;
transport.send(&endpoint, wire_bytes).await?;
Ok(())
}
#[cfg_attr(
feature = "logging",
tracing::instrument(skip_all, fields(channel_id = channel_id.0))
)]
#[allow(clippy::too_many_arguments)]
pub(super) async fn handle<
Ch: DeRecChannelStore,
Sh: DeRecShareStore,
Ss: DeRecSecretStore,
T: super::DeRecTransport,
St: DeRecStateStore,
>(
channel_store: &mut Ch,
share_store: &mut Sh,
secret_store: &mut Ss,
transport: &T,
state_store: &mut St,
message: &DeRecMessage,
secret_id: u64,
channel_id: ChannelId,
shared_key: &SharedKey,
) -> Result<Vec<DeRecEvent>> {
let inner = crate::derec_message::extract_inner_message(&message.message, shared_key)?;
if let Some(expected) = expected_role_for_inbound(&inner) {
require_role(channel_store, secret_id, &[channel_id], expected).await?;
}
let inbound_trace_id = message.trace_id;
match &inner {
MessageBody::StoreShareRequest(_) | MessageBody::StoreShareResponse(_) => {
let channel = channel_store
.load(secret_id, channel_id)
.await?
.ok_or(Error::InvalidInput(
"channel id not present in channel store",
))?;
match (channel.role, &inner) {
(SenderKind::Helper, MessageBody::StoreShareRequest(_))
| (SenderKind::Owner, MessageBody::StoreShareResponse(_)) => {
sharing::handle(channel_id, inner, *shared_key, inbound_trace_id)
}
(SenderKind::ReplicaDestination, MessageBody::StoreShareRequest(request)) => {
sharing::handle_replica_request(
secret_store,
transport,
&channel,
request.clone(),
*shared_key,
inbound_trace_id,
)
.await
}
(SenderKind::ReplicaSource, MessageBody::StoreShareResponse(response)) => {
sharing::handle_replica_response(&channel, response)
}
_ => Err(Error::RoleMismatch {
channel_id,
expected: SenderKind::Helper,
actual: channel.role,
}),
}
}
MessageBody::VerifyShareRequest(_) | MessageBody::VerifyShareResponse(_) => {
verification::handle(
share_store,
state_store,
secret_id,
channel_id,
inner,
*shared_key,
inbound_trace_id,
)
.await
}
MessageBody::GetSecretIdsVersionsRequest(_)
| MessageBody::GetSecretIdsVersionsResponse(_) => {
discovery::handle(channel_id, inner, *shared_key, inbound_trace_id)
}
MessageBody::GetShareRequest(_) | MessageBody::GetShareResponse(_) => {
recovery::handle(
state_store,
channel_id,
inner,
*shared_key,
inbound_trace_id,
secret_id,
)
.await
}
MessageBody::UnpairRequest(_) | MessageBody::UnpairResponse(_) => {
unpairing::handle(
channel_store,
share_store,
secret_store,
state_store,
secret_id,
channel_id,
inner,
*shared_key,
inbound_trace_id,
)
.await
}
MessageBody::UpdateChannelInfoRequest(_) | MessageBody::UpdateChannelInfoResponse(_) => {
update_channel_info::handle(channel_id, inner, *shared_key, inbound_trace_id).await
}
_ => Err(Error::Invariant(
"unexpected MessageBody variant in channel message",
)),
}
}
#[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_pairing<
Ch: DeRecChannelStore,
Ss: DeRecSecretStore,
T: DeRecTransport,
>(
channel_store: &mut Ch,
secret_store: &mut Ss,
transport: &T,
communication_info: &HashMap<String, String>,
message: &DeRecMessage,
secret_id: u64,
channel_id: ChannelId,
pairing_secret: &PairingSecretKeyMaterial,
replica_id: Option<u64>,
parameter_range: Option<&derec_proto::ParameterRange>,
) -> Result<Vec<DeRecEvent>> {
let inner =
crate::derec_message::extract_inner_pairing_message(&message.message, pairing_secret)?;
pairing::handle(
channel_store,
secret_store,
transport,
communication_info,
&inner,
secret_id,
channel_id,
pairing_secret,
message.trace_id,
replica_id,
parameter_range,
)
.await
}
fn expected_role_for_inbound(body: &MessageBody) -> Option<SenderKind> {
match body {
MessageBody::StoreShareRequest(_) | MessageBody::StoreShareResponse(_) => None,
MessageBody::VerifyShareRequest(_)
| MessageBody::GetSecretIdsVersionsRequest(_)
| MessageBody::GetShareRequest(_)
| MessageBody::UnpairRequest(_) => Some(SenderKind::Helper),
MessageBody::VerifyShareResponse(_)
| MessageBody::GetSecretIdsVersionsResponse(_)
| MessageBody::GetShareResponse(_)
| MessageBody::UnpairResponse(_) => Some(SenderKind::Owner),
MessageBody::UpdateChannelInfoRequest(_) | MessageBody::UpdateChannelInfoResponse(_) => {
None
}
_ => None,
}
}