use std::{
cell::{Cell, UnsafeCell},
collections::VecDeque,
marker::PhantomData,
net::SocketAddr,
rc::Rc,
time::Duration,
};
use crate::{
error::{Error, Result},
proto::{Request, Response, WebSocketCloseFrame, WebSocketMessage},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RequestId(pub u64);
impl RequestId {
pub const fn new(raw: u64) -> Self {
Self(raw)
}
pub const fn raw(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TimerId(pub u64);
impl TimerId {
pub const fn new(raw: u64) -> Self {
Self(raw)
}
pub const fn raw(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WebSocketSessionId(pub u64);
impl WebSocketSessionId {
pub const fn new(raw: u64) -> Self {
Self(raw)
}
pub const fn raw(self) -> u64 {
self.0
}
}
pub type ResponseCallback =
Box<dyn FnOnce(&mut ClientContext, RequestId, Result<Response>) + 'static>;
pub type TimerCallback = Box<dyn FnOnce(&mut ClientContext, TimerId) + 'static>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressFamily {
Ipv4,
Ipv6,
}
#[derive(Debug, Clone)]
pub struct DnsResolution {
pub host: String,
pub port: u16,
pub family: AddressFamily,
pub addrs: Vec<SocketAddr>,
pub ttl: Duration,
pub error: Option<String>,
}
impl DnsResolution {
pub fn success(
host: impl Into<String>,
port: u16,
family: AddressFamily,
addrs: Vec<SocketAddr>,
ttl: Duration,
) -> Self {
Self {
host: host.into(),
port,
family,
addrs,
ttl,
error: None,
}
}
pub fn failure(
host: impl Into<String>,
port: u16,
family: AddressFamily,
reason: impl Into<String>,
) -> Self {
Self {
host: host.into(),
port,
family,
addrs: Vec::new(),
ttl: Duration::ZERO,
error: Some(reason.into()),
}
}
pub fn is_usable(&self) -> bool {
self.error.is_none() && !self.addrs.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct DeferredResponse {
pub connection_id: u64,
pub stream_id: Option<u32>,
pub response: Response,
}
impl DeferredResponse {
pub fn new(connection_id: u64, stream_id: Option<u32>, response: Response) -> Self {
Self {
connection_id,
stream_id,
response,
}
}
}
pub struct SubmitCommand {
pub request_id: RequestId,
pub request: Request,
pub callback: ResponseCallback,
}
pub struct TimerCommand {
pub timer_id: TimerId,
pub delay: Duration,
pub callback: TimerCallback,
}
pub enum InboxCommand {
Submit(SubmitCommand),
Cancel(RequestId),
SetTimer(TimerCommand),
WebSocketSend {
session_id: WebSocketSessionId,
message: WebSocketMessage,
},
WebSocketClose {
session_id: WebSocketSessionId,
close: Option<WebSocketCloseFrame>,
},
DnsResolved(DnsResolution),
Respond(DeferredResponse),
}
struct InboxShared {
commands: UnsafeCell<VecDeque<InboxCommand>>,
closed: Cell<bool>,
next_request_id: Cell<u64>,
next_timer_id: Cell<u64>,
next_websocket_session_id: Cell<u64>,
}
impl InboxShared {
fn new() -> Self {
Self {
commands: UnsafeCell::new(VecDeque::new()),
closed: Cell::new(false),
next_request_id: Cell::new(0),
next_timer_id: Cell::new(0),
next_websocket_session_id: Cell::new(0),
}
}
fn advance(counter: &Cell<u64>) -> u64 {
let next = counter.get().wrapping_add(1);
counter.set(next);
next
}
fn push(&self, command: InboxCommand) {
if self.closed.get() {
return;
}
unsafe { &mut *self.commands.get() }.push_back(command);
}
fn pop(&self) -> Option<InboxCommand> {
unsafe { &mut *self.commands.get() }.pop_front()
}
fn len(&self) -> usize {
unsafe { &*self.commands.get() }.len()
}
fn is_empty(&self) -> bool {
unsafe { &*self.commands.get() }.is_empty()
}
}
pub struct SubsystemInbox {
shared: Rc<InboxShared>,
not_send_or_sync: PhantomData<*mut ()>,
}
impl SubsystemInbox {
pub fn new() -> Self {
Self {
shared: Rc::new(InboxShared::new()),
not_send_or_sync: PhantomData,
}
}
pub fn context(&self) -> ClientContext {
ClientContext {
shared: Rc::clone(&self.shared),
not_send_or_sync: PhantomData,
}
}
pub fn push(&self, command: InboxCommand) {
self.shared.push(command);
}
pub fn pop(&self) -> Option<InboxCommand> {
self.shared.pop()
}
pub fn len(&self) -> usize {
self.shared.len()
}
pub fn is_empty(&self) -> bool {
self.shared.is_empty()
}
pub fn next_request_id(&self) -> RequestId {
RequestId(InboxShared::advance(&self.shared.next_request_id))
}
pub fn next_timer_id(&self) -> TimerId {
TimerId(InboxShared::advance(&self.shared.next_timer_id))
}
pub fn next_websocket_session_id(&self) -> WebSocketSessionId {
const WS_ID_MASK: u64 = (1 << 52) - 1;
WebSocketSessionId(
InboxShared::advance(&self.shared.next_websocket_session_id) & WS_ID_MASK,
)
}
pub fn dns_resolved(&self, resolution: DnsResolution) {
self.push(InboxCommand::DnsResolved(resolution));
}
pub fn defer_response(&self, response: DeferredResponse) {
self.push(InboxCommand::Respond(response));
}
pub fn websocket_send(&self, session_id: WebSocketSessionId, message: WebSocketMessage) {
self.push(InboxCommand::WebSocketSend {
session_id,
message,
});
}
pub fn websocket_close(
&self,
session_id: WebSocketSessionId,
close: Option<WebSocketCloseFrame>,
) {
self.push(InboxCommand::WebSocketClose { session_id, close });
}
}
impl Clone for SubsystemInbox {
fn clone(&self) -> Self {
Self {
shared: Rc::clone(&self.shared),
not_send_or_sync: PhantomData,
}
}
}
impl Default for SubsystemInbox {
fn default() -> Self {
Self::new()
}
}
pub struct ClientContext {
shared: Rc<InboxShared>,
not_send_or_sync: PhantomData<*mut ()>,
}
impl ClientContext {
pub fn new(inbox: &SubsystemInbox) -> Self {
inbox.context()
}
pub fn submit<F>(&mut self, req: Request, on_complete: F) -> Result<RequestId>
where
F: FnOnce(&mut ClientContext, RequestId, Result<Response>) + 'static,
{
if self.shared.closed.get() {
return Err(Error::InvalidState("inbox is closed".to_owned()));
}
let request_id = RequestId(InboxShared::advance(&self.shared.next_request_id));
let callback: ResponseCallback = Box::new(on_complete);
self.stage(InboxCommand::Submit(SubmitCommand {
request_id,
request: req,
callback,
}));
Ok(request_id)
}
pub fn cancel(&mut self, request_id: RequestId) {
self.stage(InboxCommand::Cancel(request_id));
}
pub fn set_timer<F>(&mut self, delay: Duration, on_expire: F) -> TimerId
where
F: FnOnce(&mut ClientContext, TimerId) + 'static,
{
let timer_id = TimerId(InboxShared::advance(&self.shared.next_timer_id));
let callback: TimerCallback = Box::new(on_expire);
self.stage(InboxCommand::SetTimer(TimerCommand {
timer_id,
delay,
callback,
}));
timer_id
}
pub fn websocket_send(&mut self, session_id: WebSocketSessionId, msg: WebSocketMessage) {
self.stage(InboxCommand::WebSocketSend {
session_id,
message: msg,
});
}
pub fn websocket_close(
&mut self,
session_id: WebSocketSessionId,
close: Option<WebSocketCloseFrame>,
) {
self.stage(InboxCommand::WebSocketClose { session_id, close });
}
fn stage(&self, command: InboxCommand) {
self.shared.push(command);
}
}
impl Drop for SubsystemInbox {
fn drop(&mut self) {
drain_if_last(&self.shared);
}
}
impl Drop for ClientContext {
fn drop(&mut self) {
drain_if_last(&self.shared);
}
}
fn drain_if_last(shared: &Rc<InboxShared>) {
if Rc::strong_count(shared) != 1 {
return;
}
shared.closed.set(true);
let mut context = ClientContext {
shared: Rc::clone(shared),
not_send_or_sync: PhantomData,
};
while let Some(command) = shared.pop() {
if let InboxCommand::Submit(submit) = command {
(submit.callback)(&mut context, submit.request_id, Err(Error::Cancelled));
}
}
}