use std::{collections::HashMap, fmt, future::Future, net::IpAddr, pin::Pin, time::Instant};
use thiserror::Error;
use tokio_util::sync::CancellationToken;
use super::{CloseCause, FramedRecv, FramedSend};
use crate::{
zakura::{ServicePeerDirection, ZakuraConnId, ZakuraPeerId},
BoxError,
};
use super::Frame;
pub type BoxRunFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum StreamMode {
Ordered,
RequestResponse,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrderedStreamOpening {
InitiatorOnly,
EitherSide,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct OrderedStreamPolicy {
pub opening: OrderedStreamOpening,
pub reopen: bool,
}
impl Default for OrderedStreamPolicy {
fn default() -> Self {
Self {
opening: OrderedStreamOpening::InitiatorOnly,
reopen: false,
}
}
}
pub enum OrderedSessionDemand {
OpenNow,
RetryAt(Instant),
WaitForChange(BoxRunFuture<'static, ()>),
Retire,
}
impl fmt::Debug for OrderedSessionDemand {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OpenNow => formatter.write_str("OpenNow"),
Self::RetryAt(at) => formatter.debug_tuple("RetryAt").field(at).finish(),
Self::WaitForChange(_) => formatter.write_str("WaitForChange"),
Self::Retire => formatter.write_str("Retire"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Stream {
pub kind: u16,
pub version: u16,
pub frame_cap: u32,
pub capability: u64,
pub mode: StreamMode,
}
#[derive(Debug)]
pub(crate) struct ServiceStream {
pub(crate) session_id: u64,
pub(crate) version: u16,
pub(crate) recv: FramedRecv,
pub(crate) send: FramedSend,
pub(crate) cancel_token: CancellationToken,
}
impl ServiceStream {
pub(crate) fn new(
session_id: u64,
version: u16,
recv: FramedRecv,
send: FramedSend,
cancel_token: CancellationToken,
) -> Self {
Self {
session_id,
version,
recv,
send,
cancel_token,
}
}
}
#[derive(Debug)]
pub struct Peer {
pub id: ZakuraPeerId,
pub conn_id: ZakuraConnId,
pub remote_ip: Option<IpAddr>,
pub negotiated: u64,
pub direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
service_cancel_token: CancellationToken,
close_cause: CloseCause,
}
impl Peer {
pub fn new(
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction(
0,
id,
remote_ip,
negotiated,
ServicePeerDirection::Inbound,
streams,
cancel_token,
)
}
pub fn new_with_direction(
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction(
0,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
)
}
pub(crate) fn new_with_conn_id_and_direction(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction_and_close_cause(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
CloseCause::new(),
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_conn_id_and_direction_and_close_cause(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
let streams = streams
.into_iter()
.map(|(kind, (recv, send))| {
(
kind,
ServiceStream::new(0, 0, recv, send, cancel_token.child_token()),
)
})
.collect::<HashMap<_, _>>();
Self::new_with_service_streams(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
close_cause,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_service_streams(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
let service_cancel_token = streams
.values()
.next()
.map(|stream| stream.cancel_token.clone())
.unwrap_or_else(|| cancel_token.child_token());
Self::new_with_service_cancel_token(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
service_cancel_token,
close_cause,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_service_cancel_token(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
service_cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
Self {
id,
conn_id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
service_cancel_token,
close_cause,
}
}
pub fn take_stream(&mut self, kind: u16) -> Option<(FramedRecv, FramedSend)> {
self.streams
.remove(&kind)
.map(|stream| (stream.recv, stream.send))
}
pub fn take_stream_with_session_id(
&mut self,
kind: u16,
) -> Option<(u64, FramedRecv, FramedSend)> {
self.streams
.remove(&kind)
.map(|stream| (stream.session_id, stream.recv, stream.send))
}
pub fn take_versioned_stream_with_session_id(
&mut self,
kind: u16,
) -> Option<(u64, u16, FramedRecv, FramedSend)> {
self.streams
.remove(&kind)
.map(|stream| (stream.session_id, stream.version, stream.recv, stream.send))
}
pub fn cancel_token(&self) -> CancellationToken {
self.cancel_token.clone()
}
pub fn service_cancel_token(&self) -> CancellationToken {
self.service_cancel_token.clone()
}
pub(crate) fn close_cause(&self) -> CloseCause {
self.close_cause.clone()
}
pub(crate) fn into_parts(
self,
) -> (
ZakuraPeerId,
ZakuraConnId,
Option<IpAddr>,
u64,
ServicePeerDirection,
HashMap<u16, ServiceStream>,
CancellationToken,
CloseCause,
) {
(
self.id,
self.conn_id,
self.remote_ip,
self.negotiated,
self.direction,
self.streams,
self.cancel_token,
self.close_cause,
)
}
}
pub trait Service: fmt::Debug + Send + Sync + 'static {
fn name(&self) -> &'static str;
fn streams(&self) -> &[Stream];
fn ordered_stream_policy(&self, _kind: u16) -> OrderedStreamPolicy {
OrderedStreamPolicy::default()
}
fn ordered_session_demand(
&self,
_conn_id: ZakuraConnId,
peer: &ZakuraPeerId,
negotiated: u64,
direction: ServicePeerDirection,
) -> OrderedSessionDemand {
if self.wants_peer(peer, negotiated, direction) {
OrderedSessionDemand::OpenNow
} else {
OrderedSessionDemand::Retire
}
}
fn wants_peer(
&self,
_peer: &ZakuraPeerId,
_negotiated: u64,
_direction: ServicePeerDirection,
) -> bool {
true
}
fn owns_connection_for_peer(&self, _peer: &ZakuraPeerId, _conn_id: ZakuraConnId) -> bool {
false
}
fn add_peer(&self, peer: Peer);
fn remove_peer(&self, peer: &ZakuraPeerId, conn_id: ZakuraConnId);
fn deliver_frame(
&self,
_peer_id: ZakuraPeerId,
_stream_kind: u16,
_frame: Frame,
) -> Result<(), SinkReject> {
Err(SinkReject::protocol(
"service does not accept inbound frames",
))
}
fn as_request_response(&self) -> Option<&dyn RequestResponseService> {
None
}
}
pub trait RequestResponseService: Service {
fn request_frame<'a>(
&'a self,
peer_id: ZakuraPeerId,
stream_kind: u16,
request_id: u64,
max_frame_bytes: u32,
max_message_bytes: u32,
frame: Frame,
) -> BoxRunFuture<'a, Result<Vec<Frame>, SinkReject>>;
}
pub trait Sink: Send + 'static {
fn run(self: Box<Self>, recv: FramedRecv) -> BoxRunFuture<'static, Result<(), SinkReject>>;
}
pub trait Source: Send + 'static {
fn run(self: Box<Self>, send: FramedSend) -> BoxRunFuture<'static, ()>;
}
#[derive(Debug, Error)]
pub enum SinkReject {
#[error("inbound sink rejected protocol-invalid frame: {0}")]
Protocol(#[source] BoxError),
#[error("inbound sink could not accept frame locally: {0}")]
Local(#[source] BoxError),
}
impl SinkReject {
pub fn protocol(error: impl Into<BoxError>) -> Self {
Self::Protocol(error.into())
}
pub fn local(error: impl Into<BoxError>) -> Self {
Self::Local(error.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sink_reject_constructors_preserve_protocol_and_local_contract() {
let protocol = SinkReject::protocol("bad frame");
let local = SinkReject::local("closed queue");
assert!(matches!(protocol, SinkReject::Protocol(_)));
assert!(matches!(local, SinkReject::Local(_)));
assert!(protocol.to_string().contains("protocol-invalid"));
assert!(local.to_string().contains("locally"));
}
}