mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Derive session routing from hooks
This commit is contained in:
+7
-9
@@ -1,5 +1,3 @@
|
||||
use alloc::string::String;
|
||||
|
||||
// TODO: Make this seed dependent on env var;
|
||||
pub const GLOBAL_SEED: u32 = 0xDEAFBEEF;
|
||||
// pub const GLOBAL_NONCE: u32 = {
|
||||
@@ -55,17 +53,17 @@ macro_rules! hash_32 {
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn hash_string_32(input: String) -> u32 {
|
||||
// pub const fn hash_string_32(input: String) -> u32 {
|
||||
// let hash: [u8; 32] = sha256(input.as_bytes());
|
||||
// u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
||||
// }
|
||||
|
||||
pub const fn hash_str_32(input: &str) -> u32 {
|
||||
let hash: [u8; 32] = sha256(input.as_bytes());
|
||||
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
||||
}
|
||||
|
||||
pub fn hash_str_32(input: &str) -> u32 {
|
||||
let hash: [u8; 32] = sha256(input.as_bytes());
|
||||
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
||||
}
|
||||
|
||||
pub fn hash_32(input: u32) -> u32 {
|
||||
pub const fn hash_32(input: u32) -> u32 {
|
||||
let hash: [u8; 32] = sha256(&input.to_be_bytes());
|
||||
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::protocol::{Endpoint, EndpointError, EndpointName};
|
||||
|
||||
/// Compact identifier for one routed return channel.
|
||||
@@ -79,6 +81,30 @@ impl Endpoint {
|
||||
self.close_hook(hook_id)
|
||||
}
|
||||
|
||||
/// Returns the destination path for packets sent back over `hook_id`.
|
||||
///
|
||||
/// Hooks record the adjacent peer that paved the return channel. This helper turns
|
||||
/// that peer into the packet path required by the current router: parent peers map
|
||||
/// to the parent path, and child peers map to the direct child path. Session logic
|
||||
/// should not store this path itself.
|
||||
pub(crate) fn hook_path(&self, hook_id: HookID) -> Result<Vec<u32>, EndpointError> {
|
||||
let peer = self
|
||||
.hook_peer(hook_id)
|
||||
.ok_or(EndpointError::UnknownHook { hook_id })?;
|
||||
|
||||
if self.path.is_empty() {
|
||||
return Err(EndpointError::EndpointPathUnset);
|
||||
}
|
||||
|
||||
if self.path.len() > 1 && self.path[self.path.len() - 2] == peer {
|
||||
Ok(self.path[..self.path.len() - 1].to_vec())
|
||||
} else {
|
||||
let mut path = self.path.clone();
|
||||
path.push(peer);
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates that `actual_peer` is the peer allowed to use `hook_id`.
|
||||
pub(crate) fn ensure_hook_peer(
|
||||
&self,
|
||||
|
||||
@@ -123,6 +123,7 @@ macro_rules! unshell_leaf {
|
||||
== <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID
|
||||
{
|
||||
$crate::protocol::dispatch_session::<$State, $Session>(
|
||||
endpoint,
|
||||
leaf_id,
|
||||
&mut self.state,
|
||||
&mut self.$session_field,
|
||||
|
||||
+31
-12
@@ -4,7 +4,7 @@ use crate::{
|
||||
interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget},
|
||||
protocol::{
|
||||
Endpoint, Packet, PacketQueue, Procedure, ProcedureOut, Session, SessionCtx, SessionEntry,
|
||||
SessionFamily, SessionInit, SessionInitResult, SessionStatus,
|
||||
SessionFamily, SessionInit, SessionInitError, SessionStatus,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,6 +88,7 @@ impl Default for LeafOutbox {
|
||||
/// find the hook, initialize missing sessions, queue rejected responses, and update
|
||||
/// interface state when a caller supplied one.
|
||||
pub fn dispatch_session<L, S>(
|
||||
endpoint: &Endpoint,
|
||||
leaf_id: u32,
|
||||
leaf: &mut L,
|
||||
family: &mut SessionFamily<S::State>,
|
||||
@@ -131,12 +132,27 @@ pub fn dispatch_session<L, S>(
|
||||
}
|
||||
|
||||
let started_ns = interface.as_ref().and_then(|store| store.now_ns());
|
||||
let Ok(path) = endpoint.hook_path(hook_id) else {
|
||||
if let Some(store) = interface.as_mut() {
|
||||
store.record_for(
|
||||
target,
|
||||
InterfaceEventKind::SessionRejected {
|
||||
procedure_id,
|
||||
hook_id,
|
||||
started_ns,
|
||||
finished_ns: store.now_ns(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
let packet_path = packet.path.clone();
|
||||
let mut init = SessionInit::new(hook_id, packet_path);
|
||||
|
||||
match S::init(leaf, packet, &mut init) {
|
||||
SessionInitResult::Created(state) => {
|
||||
family.entries.push(SessionEntry::new(hook_id, state));
|
||||
Ok(state) => {
|
||||
family.entries.push(SessionEntry::new(hook_id, path, state));
|
||||
|
||||
if let Some(store) = interface.as_mut() {
|
||||
store.record_for(
|
||||
@@ -150,7 +166,7 @@ pub fn dispatch_session<L, S>(
|
||||
);
|
||||
}
|
||||
}
|
||||
SessionInitResult::Rejected => {
|
||||
Err(SessionInitError::Rejected) => {
|
||||
if let Some(store) = interface.as_mut() {
|
||||
store.record_for(
|
||||
target,
|
||||
@@ -163,7 +179,15 @@ pub fn dispatch_session<L, S>(
|
||||
);
|
||||
}
|
||||
}
|
||||
SessionInitResult::RejectedWith(packet) => {
|
||||
Err(SessionInitError::Response { data, end_hook }) => {
|
||||
let packet = Packet {
|
||||
hook_id,
|
||||
end_hook,
|
||||
path,
|
||||
procedure_id,
|
||||
data,
|
||||
};
|
||||
|
||||
if let Some(store) = interface.as_mut() {
|
||||
store.record_for(
|
||||
target,
|
||||
@@ -203,14 +227,9 @@ pub fn update_session_family<L, S>(
|
||||
|
||||
let started_ns = interface.as_ref().and_then(|store| store.now_ns());
|
||||
let outbox_start = entry.outbox.len();
|
||||
let reply_path = S::reply_path(&entry.state).to_vec();
|
||||
let path = entry.path.clone();
|
||||
let status = {
|
||||
let mut ctx = SessionCtx::new(
|
||||
entry.hook_id,
|
||||
reply_path,
|
||||
S::PROCEDURE_ID,
|
||||
&mut entry.outbox,
|
||||
);
|
||||
let mut ctx = SessionCtx::new(entry.hook_id, path, S::PROCEDURE_ID, &mut entry.outbox);
|
||||
|
||||
S::update(leaf, &mut entry.state, &mut entry.inbox, &mut ctx)
|
||||
};
|
||||
|
||||
+57
-36
@@ -18,16 +18,12 @@ use crate::interface::SessionView;
|
||||
/// const PROCEDURE_ID: u32 = 7;
|
||||
/// type State = MySessionState;
|
||||
///
|
||||
/// fn reply_path(state: &Self::State) -> &[u32] {
|
||||
/// &state.reply_path
|
||||
/// }
|
||||
///
|
||||
/// fn init(
|
||||
/// leaf: &mut MyLeafState,
|
||||
/// packet: Packet,
|
||||
/// ctx: &mut SessionInit,
|
||||
/// ) -> SessionInitResult<Self::State> {
|
||||
/// SessionInitResult::Created(MySessionState::from_open(leaf, packet, ctx))
|
||||
/// ) -> Result<Self::State, SessionInitError> {
|
||||
/// Ok(MySessionState::from_open(leaf, packet, ctx))
|
||||
/// }
|
||||
///
|
||||
/// fn update(
|
||||
@@ -50,20 +46,16 @@ pub trait Session<L> {
|
||||
/// Application state stored for one live hook.
|
||||
type State;
|
||||
|
||||
/// Returns the destination path for responses emitted by this session.
|
||||
///
|
||||
/// `Packet` currently carries only a destination path, so protocols that need to
|
||||
/// reply to a caller should capture a reply path during [`Self::init`]. The
|
||||
/// generated leaf clones this path into [`SessionCtx`] before calling update so
|
||||
/// session code can mutably borrow its state while emitting frames.
|
||||
fn reply_path(session: &Self::State) -> &[u32];
|
||||
|
||||
/// Creates one session state from a packet whose hook has no active session.
|
||||
///
|
||||
/// Returning [`SessionInitResult::RejectedWith`] lets the generated leaf route a
|
||||
/// protocol-level failure response with the same retry guarantees as normal
|
||||
/// output. Returning [`SessionInitResult::Rejected`] silently consumes the packet.
|
||||
fn init(leaf: &mut L, packet: Packet, ctx: &mut SessionInit) -> SessionInitResult<Self::State>;
|
||||
/// The generated runtime derives all response routing from hook state. Session
|
||||
/// initialization therefore returns only application state or a protocol-level
|
||||
/// rejection; it never stores or receives a caller reply path.
|
||||
fn init(
|
||||
leaf: &mut L,
|
||||
packet: Packet,
|
||||
ctx: &mut SessionInit,
|
||||
) -> Result<Self::State, SessionInitError>;
|
||||
|
||||
/// Advances one active hook session.
|
||||
///
|
||||
@@ -119,16 +111,42 @@ impl SessionInit {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of trying to create a session from a packet without an active hook entry.
|
||||
pub enum SessionInitResult<S> {
|
||||
/// A new session was created and should be stored by the generated leaf.
|
||||
Created(S),
|
||||
|
||||
/// The packet was intentionally consumed without creating state or a response.
|
||||
/// Error returned when a packet cannot create a new session.
|
||||
pub enum SessionInitError {
|
||||
/// The packet was intentionally consumed without creating state or sending output.
|
||||
Rejected,
|
||||
|
||||
/// The packet was rejected with a response that the generated leaf must route.
|
||||
RejectedWith(Packet),
|
||||
/// The packet was rejected with response data that should be sent on the same hook.
|
||||
Response {
|
||||
/// Raw `Packet::data` for the response frame.
|
||||
data: Vec<u8>,
|
||||
|
||||
/// Whether the response should close the hook after successful routing.
|
||||
end_hook: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl SessionInitError {
|
||||
/// Creates a silent session rejection.
|
||||
pub fn rejected() -> Self {
|
||||
Self::Rejected
|
||||
}
|
||||
|
||||
/// Creates a non-final response for a rejected session open.
|
||||
pub fn response(data: Vec<u8>) -> Self {
|
||||
Self::Response {
|
||||
data,
|
||||
end_hook: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a final response for a rejected session open.
|
||||
pub fn response_final(data: Vec<u8>) -> Self {
|
||||
Self::Response {
|
||||
data,
|
||||
end_hook: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Session lifecycle status returned from [`Session::update`].
|
||||
@@ -153,7 +171,7 @@ pub enum SessionStatus {
|
||||
/// routing in generated code is what makes final-frame retries reliable.
|
||||
pub struct SessionCtx<'a> {
|
||||
hook_id: HookID,
|
||||
reply_path: Vec<u32>,
|
||||
path: Vec<u32>,
|
||||
procedure_id: u32,
|
||||
outbox: &'a mut PacketQueue,
|
||||
}
|
||||
@@ -162,13 +180,13 @@ impl<'a> SessionCtx<'a> {
|
||||
/// Creates a context for one session update call.
|
||||
pub fn new(
|
||||
hook_id: HookID,
|
||||
reply_path: Vec<u32>,
|
||||
path: Vec<u32>,
|
||||
procedure_id: u32,
|
||||
outbox: &'a mut PacketQueue,
|
||||
) -> Self {
|
||||
Self {
|
||||
hook_id,
|
||||
reply_path,
|
||||
path,
|
||||
procedure_id,
|
||||
outbox,
|
||||
}
|
||||
@@ -179,11 +197,6 @@ impl<'a> SessionCtx<'a> {
|
||||
self.hook_id
|
||||
}
|
||||
|
||||
/// Returns the destination path used for packets emitted through this context.
|
||||
pub fn reply_path(&self) -> &[u32] {
|
||||
&self.reply_path
|
||||
}
|
||||
|
||||
/// Queues a one-byte-opcode frame without closing the hook.
|
||||
pub fn send(&mut self, opcode: u8, data: &[u8]) {
|
||||
self.send_frame(opcode, data, false);
|
||||
@@ -233,7 +246,7 @@ impl<'a> SessionCtx<'a> {
|
||||
self.outbox.push_back(Packet {
|
||||
hook_id: self.hook_id,
|
||||
end_hook,
|
||||
path: self.reply_path.clone(),
|
||||
path: self.path.clone(),
|
||||
procedure_id: self.procedure_id,
|
||||
data,
|
||||
});
|
||||
@@ -249,6 +262,13 @@ pub struct SessionEntry<S> {
|
||||
/// Hook id associated with this live session.
|
||||
pub hook_id: HookID,
|
||||
|
||||
/// Destination path for packets emitted on this hook.
|
||||
///
|
||||
/// This is generated runtime state, not user session state. It is captured from
|
||||
/// endpoint hook routing when the session is created so leaf sessions never have
|
||||
/// to carry or understand a reply path.
|
||||
pub path: Vec<u32>,
|
||||
|
||||
/// Application-owned session state.
|
||||
pub state: S,
|
||||
|
||||
@@ -300,9 +320,10 @@ impl<S> Default for SessionFamily<S> {
|
||||
|
||||
impl<S> SessionEntry<S> {
|
||||
/// Creates one active session entry for `hook_id`.
|
||||
pub fn new(hook_id: HookID, state: S) -> Self {
|
||||
pub fn new(hook_id: HookID, path: Vec<u32>, state: S) -> Self {
|
||||
Self {
|
||||
hook_id,
|
||||
path,
|
||||
state,
|
||||
inbox: PacketQueue::new(),
|
||||
outbox: PacketQueue::new(),
|
||||
|
||||
Reference in New Issue
Block a user