use super::{
error::fail,
session::{notification_callback, Callback},
IpcCodec, IpcError, IpcErrorCode, IpcFuture, IpcLimits, NotificationContext, RequestContext,
};
use std::{collections::HashMap, future::Future, sync::Arc};
pub struct Host;
pub struct Renderer;
pub trait Rpc: Send + Sync + 'static {
type Request: IpcCodec;
type Response: IpcCodec;
type Receiver: Send + Sync + 'static;
const ID: u32;
}
pub trait Event: Send + Sync + 'static {
type Payload: IpcCodec + Clone;
type Receiver: Send + Sync + 'static;
const ID: u32;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Endpoint {
Host,
Renderer,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MethodKind {
Rpc,
Notification,
}
pub type PayloadValidator = fn(&[u8], &IpcLimits) -> Result<(), IpcError>;
pub struct MethodDescriptor {
pub id: u32,
pub name: &'static str,
pub receiver: Endpoint,
pub kind: MethodKind,
pub development_only: bool,
pub validate_request: PayloadValidator,
pub validate_response: Option<PayloadValidator>,
}
pub struct IpcSchema {
pub name: &'static str,
pub major: u32,
pub hash: &'static str,
pub methods: &'static [MethodDescriptor],
}
pub(super) type Handler =
dyn Fn(RequestContext, Vec<u8>, IpcLimits) -> IpcFuture<Vec<u8>> + Send + Sync;
#[derive(Default)]
pub struct IpcRegistry {
pub(super) schema: Option<&'static IpcSchema>,
pub(super) handlers: HashMap<u32, Arc<Handler>>,
pub(super) notifications: HashMap<u32, Arc<Callback>>,
}
impl IpcRegistry {
pub fn new(schema: &'static IpcSchema) -> Self {
Self {
schema: Some(schema),
handlers: HashMap::new(),
notifications: HashMap::new(),
}
}
pub fn is_enabled(&self) -> bool {
self.schema.is_some()
}
pub fn schema(&self) -> Option<&'static IpcSchema> {
self.schema
}
pub fn validate_registration(&self, ids: &[u32]) -> Result<(), IpcError> {
for (index, id) in ids.iter().enumerate() {
let method = self
.schema
.and_then(|schema| schema.methods.iter().find(|method| method.id == *id))
.ok_or_else(|| fail(IpcErrorCode::UnknownMethod))?;
if method.receiver != Endpoint::Host {
return Err(fail(IpcErrorCode::InvalidFrame));
}
if self.handlers.contains_key(id)
|| self.notifications.contains_key(id)
|| ids[..index].contains(id)
{
return Err(fail(IpcErrorCode::InvalidPayload));
}
}
Ok(())
}
pub fn register_notification<E, F, Fut>(&mut self, callback: F) -> Result<(), IpcError>
where
E: Event<Receiver = Host>,
F: Fn(NotificationContext, E::Payload) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), IpcError>> + Send + 'static,
{
self.validate_registration(&[E::ID])?;
let method = self.method(E::ID, Endpoint::Host, MethodKind::Notification)?;
self.notifications
.insert(E::ID, notification_callback::<E, F, Fut>(method, callback));
Ok(())
}
pub fn register<M, F, Fut>(&mut self, handler: F) -> Result<(), IpcError>
where
M: Rpc<Receiver = Host>,
F: Fn(RequestContext, M::Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<M::Response, IpcError>> + Send + 'static,
{
self.validate_registration(&[M::ID])?;
let method = self.method(M::ID, Endpoint::Host, MethodKind::Rpc)?;
let handler = Arc::new(handler);
self.handlers.insert(
M::ID,
Arc::new(move |ctx, bytes, limits| {
let handler = Arc::clone(&handler);
Box::pin(async move {
(method.validate_request)(&bytes, &limits)?;
let request = M::Request::decode_ipc(bytes.as_slice())?;
let response = handler(ctx, request).await?;
let payload = response.encode_ipc();
if payload.len() > limits.max_frame_bytes.saturating_sub(64) {
return Err(fail(IpcErrorCode::PayloadTooLarge));
}
let validate = method
.validate_response
.ok_or_else(|| fail(IpcErrorCode::InvalidPayload))?;
validate(&payload, &limits)?;
Ok(payload)
})
}),
);
Ok(())
}
pub(super) fn method(
&self,
id: u32,
endpoint: Endpoint,
kind: MethodKind,
) -> Result<&'static MethodDescriptor, IpcError> {
let method = self
.schema
.and_then(|schema| schema.methods.iter().find(|m| m.id == id))
.ok_or_else(|| fail(IpcErrorCode::UnknownMethod))?;
if method.receiver != endpoint || method.kind != kind {
return Err(fail(IpcErrorCode::InvalidFrame));
}
Ok(method)
}
pub(super) fn validate(&self) -> Result<(), IpcError> {
let Some(schema) = self.schema else {
return Ok(());
};
if schema.name.is_empty()
|| schema.name.len() > 256
|| schema.major == 0
|| schema.hash.len() != 64
|| !schema
.hash
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
{
return Err(fail(IpcErrorCode::SchemaMismatch));
}
for (index, method) in schema.methods.iter().enumerate() {
if method.id <= 1023
|| schema.methods[..index].iter().any(|m| m.id == method.id)
|| (method.kind == MethodKind::Rpc) != method.validate_response.is_some()
{
return Err(fail(IpcErrorCode::SchemaMismatch));
}
}
Ok(())
}
}