use std::sync::Arc;
use crate::{
keyhive::{CliConnKeyhiveAdapter, FsKeyhiveStorage},
policy::CliKeyhivePolicyHandle,
transport::UnifiedTransport,
wire::CliWireMessage,
};
use future_form::Sendable;
use futures::future::BoxFuture;
use sedimentree_core::depth::CountLeadingZeroBytes;
use subduction_core::{
authenticated::Authenticated,
connection::message::SyncMessage,
handler::{Handler, sync::SyncHandler},
peer::id::PeerId,
remote_heads::{RemoteHeads, RemoteHeadsNotifier},
storage::metrics::MetricsStorage,
subduction::error::{IoError, ListenError},
transport::message::MessageTransport,
};
use subduction_ephemeral::{
clock::std_clock::StdClock, handler::EphemeralHandler, policy::OpenEphemeralPolicy,
};
use subduction_keyhive::handler::{SendableKeyhiveHandler, SendableRuntimeProtocol};
use subduction_redb_storage::RedbStorage;
use subduction_websocket::tokio::TokioSpawn;
pub(crate) type CliConn = MessageTransport<UnifiedTransport>;
pub(crate) type CliEphemeralHandler =
EphemeralHandler<Sendable, CliConn, OpenEphemeralPolicy, StdClock, TokioSpawn>;
pub(crate) type CliKeyhiveProtocol =
Arc<SendableRuntimeProtocol<CliConnKeyhiveAdapter, FsKeyhiveStorage>>;
pub(crate) type CliKeyhiveHandler = SendableKeyhiveHandler<
CliConnKeyhiveAdapter,
FsKeyhiveStorage,
CliConn,
fn(Authenticated<CliConn, Sendable>) -> CliConnKeyhiveAdapter,
>;
type CliListenError = ListenError<Sendable, MetricsStorage<RedbStorage>, CliConn, CliWireMessage>;
pub(crate) trait CliWireHandler:
Handler<Sendable, CliConn, Message = CliWireMessage, HandlerError = CliListenError>
+ RemoteHeadsNotifier
+ Send
+ Sync
+ 'static
{
}
impl<H> CliWireHandler for H where
H: Handler<Sendable, CliConn, Message = CliWireMessage, HandlerError = CliListenError>
+ RemoteHeadsNotifier
+ Send
+ Sync
+ 'static
{
}
pub(crate) type CliSyncHandler = Arc<
SyncHandler<
Sendable,
MetricsStorage<RedbStorage>,
CliConn,
CliKeyhivePolicyHandle,
CountLeadingZeroBytes,
TokioSpawn,
>,
>;
pub(crate) struct CliHandlerOpenPolicy {
sync: CliSyncHandler,
ephemeral: CliEphemeralHandler,
}
impl CliHandlerOpenPolicy {
pub(crate) const fn new(sync: CliSyncHandler, ephemeral: CliEphemeralHandler) -> Self {
Self { sync, ephemeral }
}
async fn handle_sync(
&self,
conn: &Authenticated<CliConn, Sendable>,
sync_msg: SyncMessage,
) -> Result<(), CliListenError> {
Handler::<Sendable, CliConn>::handle(self.sync.as_ref(), conn, sync_msg)
.await
.map_err(convert_sync_listen_error)
}
async fn handle_ephemeral(
&self,
conn: &Authenticated<CliConn, Sendable>,
eph_msg: subduction_ephemeral::message::EphemeralMessage,
) {
if let Err(e) = Handler::<Sendable, CliConn>::handle(&self.ephemeral, conn, eph_msg).await {
tracing::error!(error = %e, "ephemeral handler error (non-fatal)");
}
}
async fn disconnect_core(&self, peer: PeerId) {
Handler::<Sendable, CliConn>::on_peer_disconnect(self.sync.as_ref(), peer).await;
Handler::<Sendable, CliConn>::on_peer_disconnect(&self.ephemeral, peer).await;
}
}
pub(crate) struct CliHandler {
core: CliHandlerOpenPolicy,
keyhive: CliKeyhiveHandler,
}
impl CliHandler {
pub(crate) const fn new(
sync: CliSyncHandler,
ephemeral: CliEphemeralHandler,
keyhive: CliKeyhiveHandler,
) -> Self {
Self {
core: CliHandlerOpenPolicy::new(sync, ephemeral),
keyhive,
}
}
}
impl core::fmt::Debug for CliHandler {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CliHandler").finish_non_exhaustive()
}
}
impl core::fmt::Debug for CliHandlerOpenPolicy {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CliHandlerOpenPolicy")
.finish_non_exhaustive()
}
}
impl RemoteHeadsNotifier for CliHandler {
fn notify_remote_heads(
&self,
id: sedimentree_core::id::SedimentreeId,
peer: PeerId,
heads: RemoteHeads,
) {
self.core.notify_remote_heads(id, peer, heads);
}
}
impl RemoteHeadsNotifier for CliHandlerOpenPolicy {
fn notify_remote_heads(
&self,
id: sedimentree_core::id::SedimentreeId,
peer: PeerId,
heads: RemoteHeads,
) {
RemoteHeadsNotifier::notify_remote_heads(self.sync.as_ref(), id, peer, heads);
}
}
impl Handler<Sendable, CliConn> for CliHandler {
type Message = CliWireMessage;
type HandlerError = CliListenError;
fn handle<'a>(
&'a self,
conn: &'a Authenticated<CliConn, Sendable>,
message: CliWireMessage,
) -> BoxFuture<'a, Result<(), Self::HandlerError>> {
Box::pin(async move {
match message {
CliWireMessage::Sync(sync_msg) => self.core.handle_sync(conn, *sync_msg).await,
CliWireMessage::Ephemeral(eph_msg) => {
self.core.handle_ephemeral(conn, eph_msg).await;
Ok(())
}
CliWireMessage::Keyhive(keyhive_msg) => {
if let Err(e) =
Handler::<Sendable, CliConn>::handle(&self.keyhive, conn, keyhive_msg).await
{
tracing::error!(error = %e, "keyhive handler error (non-fatal)");
}
Ok(())
}
}
})
}
fn on_peer_disconnect(&self, peer: PeerId) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.core.disconnect_core(peer).await;
Handler::<Sendable, CliConn>::on_peer_disconnect(&self.keyhive, peer).await;
})
}
}
impl Handler<Sendable, CliConn> for CliHandlerOpenPolicy {
type Message = CliWireMessage;
type HandlerError = CliListenError;
fn handle<'a>(
&'a self,
conn: &'a Authenticated<CliConn, Sendable>,
message: CliWireMessage,
) -> BoxFuture<'a, Result<(), Self::HandlerError>> {
Box::pin(async move {
match message {
CliWireMessage::Sync(sync_msg) => self.handle_sync(conn, *sync_msg).await,
CliWireMessage::Ephemeral(eph_msg) => {
self.handle_ephemeral(conn, eph_msg).await;
Ok(())
}
CliWireMessage::Keyhive(_keyhive_msg) => Ok(()),
}
})
}
fn on_peer_disconnect(&self, peer: PeerId) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.disconnect_core(peer).await;
})
}
}
fn convert_sync_listen_error(
err: ListenError<
Sendable,
MetricsStorage<RedbStorage>,
CliConn,
subduction_core::connection::message::SyncMessage,
>,
) -> CliListenError {
match err {
ListenError::IoError(io_err) => ListenError::IoError(match io_err {
IoError::Storage(e) => IoError::Storage(e),
IoError::ConnSend(e) => IoError::ConnSend(e),
IoError::ConnRecv(e) => IoError::ConnRecv(e),
IoError::ConnCall(e) => IoError::ConnCall(e),
IoError::BlobMismatch(e) => IoError::BlobMismatch(e),
}),
ListenError::TrySendError => ListenError::TrySendError,
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic)]
mod tests {
use future_form::Sendable;
use sedimentree_core::id::SedimentreeId;
use subduction_core::{
connection::message::{
BatchSyncResponse, RemoveSubscriptions, RequestId, SyncMessage, SyncResult,
TryAsBatchSyncResponse,
},
peer::id::PeerId,
remote_heads::RemoteHeads,
timestamp::TimestampSeconds,
};
use subduction_crypto::{signed::Signed, signer::memory::MemorySigner};
use subduction_ephemeral::{
message::{EphemeralMessage, EphemeralPayload},
topic::Topic,
};
use subduction_keyhive::KeyhiveMessage;
use crate::wire::CliWireMessage;
fn test_peer_id() -> PeerId {
PeerId::new([42u8; 32])
}
fn test_request_id() -> RequestId {
RequestId {
requestor: test_peer_id(),
nonce: 99,
}
}
#[test]
fn try_as_batch_sync_response_extracts_from_sync() {
let resp = BatchSyncResponse {
req_id: test_request_id(),
id: SedimentreeId::new([0xAA; 32]),
result: SyncResult::NotFound,
responder_heads: RemoteHeads::default(),
};
let msg = CliWireMessage::Sync(Box::new(SyncMessage::BatchSyncResponse(resp.clone())));
assert_eq!(msg.try_as_batch_sync_response(), Some(&resp));
}
#[test]
fn try_as_batch_sync_response_none_for_other_sync() {
let msg = CliWireMessage::Sync(Box::new(SyncMessage::RemoveSubscriptions(
RemoveSubscriptions {
ids: vec![SedimentreeId::new([0xBB; 32])],
},
)));
assert_eq!(msg.try_as_batch_sync_response(), None);
}
#[tokio::test]
async fn try_as_batch_sync_response_none_for_ephemeral() {
let signer = MemorySigner::generate();
let ep = EphemeralPayload {
id: Topic::new([0xCC; 32]),
nonce: 42,
timestamp: TimestampSeconds::new(1_700_000_000),
payload: vec![1, 2, 3],
};
let verified = Signed::seal::<Sendable, _>(&signer, ep).await;
let msg = CliWireMessage::Ephemeral(EphemeralMessage::Ephemeral(Box::new(
verified.into_signed(),
)));
assert_eq!(msg.try_as_batch_sync_response(), None);
}
#[test]
fn try_as_batch_sync_response_none_for_keyhive() {
let msg = CliWireMessage::Keyhive(KeyhiveMessage::new(vec![0xDE, 0xAD]));
assert_eq!(msg.try_as_batch_sync_response(), None);
}
}