use dope::core::driver::{receive::credit, route};
use std::{cell, convert, marker, num, sync::Arc};
use dope::net::{self, wire};
use o3::{
base::marker::ThreadBound,
buffer::pool,
depot::{arena::pool::recycle, capacity},
};
use wire::{domain, open};
use crate::error;
use crate::state::buffers;
use crate::tls::receive::waiters;
use crate::tls::{self, client, roles};
use rustls::pki_types::ServerName;
pub mod layout;
pub struct Sessions<R: roles::Protocol = roles::Server, ID: route::Identity = route::KeyTag<0>> {
capacity: capacity::Count,
buffers: cell::OnceCell<Pools>,
role: marker::PhantomData<fn() -> (R, ID)>,
_bound: ThreadBound,
}
impl<R: roles::Protocol, ID: route::Identity> Sessions<R, ID> {
pub fn try_new(cap: usize) -> Result<Self, capacity::Error> {
Ok(Self::from_count(capacity::Count::try_from(cap)?))
}
pub(super) fn from_count(capacity: capacity::Count) -> Self {
Self {
capacity,
buffers: cell::OnceCell::new(),
role: marker::PhantomData,
_bound: ThreadBound::NEW,
}
}
}
impl<R: roles::Protocol> domain::Bootstrap<tls::Tls<R>> for tls::protocol::Bootstrap<R> {
fn try_runtime<'w, 'd, ID: route::Identity + 'w>(
limits: domain::Limits,
bound: Bound<'w, R, ID>,
) -> Result<Runtime<'w, 'd, R, ID>, net::Resource>
where
tls::Tls<R>: 'w,
'd: 'w,
{
let layout = bound.plan.layout(limits)?;
let Bound {
role,
storage: store,
..
} = bound;
if store.buffers.get().is_some() {
return Err(net::Resource::Binding(net::BindingError::AlreadyBound));
}
let init = Pools {
sessions: recycle::Store::try_new(store.capacity).map_err(net::Resource::Allocation)?,
recv: match layout.growing {
Some(warm) => buffers::Receive::Growing(
pool::Growing::try_warm(layout.recv, warm).map_err(net::Resource::Pool)?,
),
None => buffers::Receive::Fixed(
pool::Pool::try_from(layout.recv).map_err(net::Resource::Exhausted)?,
),
},
send: pool::Pool::try_from(layout.send).map_err(net::Resource::Exhausted)?,
};
let (waiters, init) = (
waiters::Queue::try_new(limits.connections()).map_err(net::Resource::Queue)?,
init,
);
if store.buffers.get().is_some() {
return Err(net::Resource::Binding(net::BindingError::AlreadyBound));
}
let pools = store.buffers.get_or_init(|| init);
Ok(Runtime {
pending: None,
buffers: pools,
waiters,
role,
})
}
}
pub struct Bound<'d, R: roles::Protocol = roles::Server, ID: route::Identity = route::KeyTag<0>> {
pub(super) plan: layout::Plan,
pub(super) role: R::Config,
pub(super) storage: &'d Sessions<R, ID>,
}
pub struct Config<R: roles::Protocol = roles::Server> {
pub(super) role: R::Config,
plan: layout::Plan,
_bound: ThreadBound,
}
impl<R: roles::Protocol> Config<R> {
pub(super) fn from_role(role: R::Config) -> Self {
Self {
role,
plan: layout::Plan::new(),
_bound: ThreadBound::NEW,
}
}
#[doc(hidden)]
pub fn bind<'d, ID: route::Identity>(self, store: &'d Sessions<R, ID>) -> Bound<'d, R, ID> {
let Self { role, plan, .. } = self;
Bound {
plan,
role,
storage: store,
}
}
pub fn with_staged(mut self, max: layout::Staged) -> Self {
self.plan = self.plan.with_staged(max);
self
}
pub fn with_send(mut self, max: num::NonZero<usize>) -> Self {
self.plan = self.plan.with_send(max);
self
}
pub fn with_growth(mut self, warm: num::NonZero<u32>) -> Self {
self.plan = self.plan.with_growth(warm);
self
}
pub const fn plan(&self) -> layout::Plan {
self.plan
}
}
impl<R: roles::Protocol, ID: route::Identity> domain::Plan<tls::Tls<R>, ID> for Config<R> {
type Error = convert::Infallible;
fn bind<'d>(self, store: &'d Sessions<R, ID>) -> Result<Bound<'d, R, ID>, Self::Error>
where
tls::Tls<R>: 'd,
ID: 'd,
{
Ok(Config::bind(self, store))
}
}
impl From<client::Plan> for Config<roles::Client> {
fn from(plan: client::Plan) -> Self {
Self::from_role(plan)
}
}
impl Config<roles::Client> {
pub fn client(config: Arc<rustls::ClientConfig>, name: ServerName<'static>) -> Self {
Self::from_role(client::Plan::new(config, name))
}
}
impl From<Arc<rustls::ServerConfig>> for Config<roles::Server> {
fn from(config: Arc<rustls::ServerConfig>) -> Self {
Self::from_role(config)
}
}
pub(super) struct Pools {
sessions: recycle::Store<()>,
pub(super) recv: buffers::Receive,
pub(super) send: pool::Pool,
}
pub struct Runtime<'w, 'd, R: roles::Protocol + 'w, ID: route::Identity + 'w>
where
'd: 'w,
{
pub(super) pending: Option<(tls::Connection<'w, R, ID>, tls::SendState<'w>)>,
pub(super) buffers: &'w Pools,
pub(super) waiters: waiters::Queue<'d, ID>,
pub(super) role: R::Config,
}
impl<ID: route::Identity> domain::Reconfigure for Runtime<'_, '_, roles::Server, ID> {
type Config = Arc<rustls::ServerConfig>;
fn reconfigure(&mut self, config: Self::Config) {
self.pending = None;
self.role = config;
}
}
impl<R: roles::Protocol, ID: route::Identity> Runtime<'_, '_, R, ID> {
fn ready(&self) -> Option<waiters::Resource> {
let resource = if self.waiters.has_waiters(waiters::Resource::Recv) {
waiters::Resource::Recv
} else if self.waiters.has_waiters(waiters::Resource::HandshakeEgress)
&& self.buffers.send.available() != 0
{
waiters::Resource::HandshakeEgress
} else {
return None;
};
(self.buffers.recv.available() != 0).then_some(resource)
}
}
impl<'w, 'd, R: roles::Protocol + 'w, ID: route::Identity + 'w>
open::Runtime<'w, 'd, tls::Tls<R>, ID> for Runtime<'w, 'd, R, ID>
where
'd: 'w,
{
fn is_ready(&self) -> bool {
self.ready().is_some()
}
fn take_ready(&mut self) -> Option<credit::Held<'d, ID>> {
self.waiters.take(self.ready()?)
}
fn open<C, T>(
&mut self,
context: C,
consume: impl FnOnce(
C,
tls::Connection<'w, R, ID>,
tls::SendState<'w>,
)
-> open::Disposition<T, tls::Connection<'w, R, ID>, tls::SendState<'w>>,
) -> open::Outcome<C, T, open::Error<'w, tls::Tls<R>>> {
let parts = match self.pending.take() {
Some(parts) => parts,
None => {
let Some(lease) = self.buffers.sessions.take() else {
return open::Outcome::Unavailable(context);
};
let pending = if R::SEND_ON_OPEN {
let Some(cursor) = self.buffers.send.view() else {
return open::Outcome::Unavailable(context);
};
Some(cursor)
} else {
None
};
let opened = R::open(&self.role, roles::Contract(())).and_then(|conn| {
tls::Connection::new(
conn,
self.buffers,
lease,
tls::SendState { buffer: pending },
)
});
match opened {
Ok(parts) => parts,
Err(error::Error::BufferUnavailable) => {
return open::Outcome::Unavailable(context);
}
Err(cause) => {
return open::Outcome::Failed {
context,
cause: open::Error::from_cause(cause),
};
}
}
}
};
let (wire, send) = parts;
let output = consume(context, wire, send).into_output(|wire, send| {
self.pending = Some((wire, send));
});
open::Outcome::Opened(output)
}
}