blob: 6ea91a6accc47d3b3dc5af91e9a8634128c0874e [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! MCTP IPC request dispatch.
//!
//! Decodes wire-protocol requests and dispatches them to the [`Server`].
//! This is the server-side counterpart of `openprot-mctp-client`.
use openprot_mctp_api::wire::{self, flags, MctpOp, MctpRequestHeader};
use openprot_mctp_api::{Handle, ResponseCode};
use crate::{RecvResult, Sender, Server};
/// Outcome of [`dispatch_mctp_op`].
pub enum DispatchOutcome {
/// Response is ready; `response[..n]` bytes are filled.
Reply(usize),
/// No message was available for `Recv`; the call has been registered
/// as pending. The platform must store its reply token keyed on
/// `handle` and call [`drive_pending`] when new data arrives or on
/// each timer tick.
Pending {
/// The handle whose recv was deferred.
handle: Handle,
},
}
/// Dispatch an IPC request to the MCTP server.
///
/// Decodes the request header, calls the appropriate `Server` method,
/// and encodes the response into `response`.
///
/// `now_millis` is the current monotonic time used to set recv deadlines.
///
/// Returns `DispatchOutcome::Reply(n)` when a response is immediately
/// available, or `DispatchOutcome::Pending { handle }` when a `Recv`
/// has been registered and will be fulfilled later by [`drive_pending`].
pub fn dispatch_mctp_op<S: Sender, const N: usize>(
request: &[u8],
response: &mut [u8],
server: &mut Server<S, N>,
recv_buf: &mut [u8],
now_millis: u64,
) -> DispatchOutcome {
let header = match MctpRequestHeader::from_bytes(request) {
Some(h) => h,
None => return DispatchOutcome::Reply(encode_error(response, ResponseCode::BadArgument)),
};
let Some(op) = header.operation() else {
return DispatchOutcome::Reply(encode_error(response, ResponseCode::BadArgument));
};
let n = match op {
MctpOp::SetEid => match server.set_eid(header.eid) {
Ok(()) => encode_success(response),
Err(e) => encode_error(response, e.code),
},
MctpOp::GetEid => {
let eid = server.get_eid();
wire::encode_get_eid_response(response, eid)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError))
}
MctpOp::Listener => match server.listener(header.msg_type) {
Ok(handle) => wire::encode_handle_response(response, handle.0)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError)),
Err(e) => encode_error(response, e.code),
},
MctpOp::Req => match server.req(header.eid) {
Ok(handle) => wire::encode_handle_response(response, handle.0)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError)),
Err(e) => encode_error(response, e.code),
},
MctpOp::Recv => {
let handle = Handle(header.handle);
match server.try_recv(handle, recv_buf) {
Some(meta) => {
let payload = &recv_buf[..meta.payload_size];
wire::encode_recv_response(
response,
meta.msg_type,
meta.msg_ic,
meta.remote_eid,
meta.msg_tag,
payload,
)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError))
}
None => {
let timeout = wire::get_recv_timeout(request);
let _ = server.register_recv(handle, timeout, now_millis);
return DispatchOutcome::Pending { handle };
}
}
}
MctpOp::Send => {
let handle = if header.flags & flags::HAS_HANDLE != 0 {
Some(Handle(header.handle))
} else {
None
};
let eid = if header.flags & flags::HAS_EID != 0 {
Some(header.eid)
} else {
None
};
let tag = if header.flags & flags::HAS_TAG != 0 {
Some(header.tag)
} else {
None
};
let ic = header.flags & flags::IC != 0;
let payload = wire::get_request_payload(request);
match server.send(handle, header.msg_type, eid, tag, ic, payload) {
Ok(tag_val) => wire::encode_send_response(response, tag_val)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError)),
Err(e) => encode_error(response, e.code),
}
}
MctpOp::Unbind => {
let handle = Handle(header.handle);
match server.unbind(handle) {
Ok(()) => encode_success(response),
Err(e) => encode_error(response, e.code),
}
}
};
DispatchOutcome::Reply(n)
}
/// Drive pending receive calls to completion.
///
/// Call this on timer ticks and after feeding inbound packets to the server.
/// For each handle that is now ready (message arrived or timed out),
/// `on_ready(handle, response_len)` is called with `response` filled.
/// The platform must look up its stored reply token for `handle` and send
/// the response through it.
pub fn drive_pending<S: Sender, const N: usize>(
server: &mut Server<S, N>,
now_millis: u64,
recv_buf: &mut [u8],
response: &mut [u8],
mut on_ready: impl FnMut(Handle, usize),
) {
let (_, ready) = server.update(now_millis, recv_buf);
for (handle, result) in ready {
let len = match result {
RecvResult::Message(meta) => {
let payload = &recv_buf[..meta.payload_size];
wire::encode_recv_response(
response,
meta.msg_type,
meta.msg_ic,
meta.remote_eid,
meta.msg_tag,
payload,
)
.unwrap_or_else(|_| encode_error(response, ResponseCode::InternalError))
}
RecvResult::TimedOut => encode_error(response, ResponseCode::TimedOut),
};
on_ready(handle, len);
}
}
fn encode_error(response: &mut [u8], code: ResponseCode) -> usize {
wire::encode_error_response(response, code).unwrap_or(0)
}
fn encode_success(response: &mut [u8]) -> usize {
wire::encode_success_response(response).unwrap_or(0)
}