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:
Generated
+7
@@ -829,6 +829,13 @@ dependencies = [
|
|||||||
"unshell",
|
"unshell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaf-shell"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"unshell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ members = [
|
|||||||
"ush-obfuscate",
|
"ush-obfuscate",
|
||||||
"base62",
|
"base62",
|
||||||
|
|
||||||
"unshell-leaves/leaf-pty",
|
"unshell-leaves/leaf-pty", "unshell-leaves/leaf-shell",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -1,5 +1,3 @@
|
|||||||
use alloc::string::String;
|
|
||||||
|
|
||||||
// TODO: Make this seed dependent on env var;
|
// TODO: Make this seed dependent on env var;
|
||||||
pub const GLOBAL_SEED: u32 = 0xDEAFBEEF;
|
pub const GLOBAL_SEED: u32 = 0xDEAFBEEF;
|
||||||
// pub const GLOBAL_NONCE: u32 = {
|
// 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());
|
let hash: [u8; 32] = sha256(input.as_bytes());
|
||||||
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_str_32(input: &str) -> u32 {
|
pub const fn hash_32(input: u32) -> 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 {
|
|
||||||
let hash: [u8; 32] = sha256(&input.to_be_bytes());
|
let hash: [u8; 32] = sha256(&input.to_be_bytes());
|
||||||
u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]])
|
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};
|
use crate::protocol::{Endpoint, EndpointError, EndpointName};
|
||||||
|
|
||||||
/// Compact identifier for one routed return channel.
|
/// Compact identifier for one routed return channel.
|
||||||
@@ -79,6 +81,30 @@ impl Endpoint {
|
|||||||
self.close_hook(hook_id)
|
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`.
|
/// Validates that `actual_peer` is the peer allowed to use `hook_id`.
|
||||||
pub(crate) fn ensure_hook_peer(
|
pub(crate) fn ensure_hook_peer(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ macro_rules! unshell_leaf {
|
|||||||
== <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID
|
== <$Session as $crate::protocol::Session<$State>>::PROCEDURE_ID
|
||||||
{
|
{
|
||||||
$crate::protocol::dispatch_session::<$State, $Session>(
|
$crate::protocol::dispatch_session::<$State, $Session>(
|
||||||
|
endpoint,
|
||||||
leaf_id,
|
leaf_id,
|
||||||
&mut self.state,
|
&mut self.state,
|
||||||
&mut self.$session_field,
|
&mut self.$session_field,
|
||||||
|
|||||||
+31
-12
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget},
|
interface::{InterfaceEventKind, InterfaceStore, InterfaceTarget},
|
||||||
protocol::{
|
protocol::{
|
||||||
Endpoint, Packet, PacketQueue, Procedure, ProcedureOut, Session, SessionCtx, SessionEntry,
|
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
|
/// find the hook, initialize missing sessions, queue rejected responses, and update
|
||||||
/// interface state when a caller supplied one.
|
/// interface state when a caller supplied one.
|
||||||
pub fn dispatch_session<L, S>(
|
pub fn dispatch_session<L, S>(
|
||||||
|
endpoint: &Endpoint,
|
||||||
leaf_id: u32,
|
leaf_id: u32,
|
||||||
leaf: &mut L,
|
leaf: &mut L,
|
||||||
family: &mut SessionFamily<S::State>,
|
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 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 packet_path = packet.path.clone();
|
||||||
let mut init = SessionInit::new(hook_id, packet_path);
|
let mut init = SessionInit::new(hook_id, packet_path);
|
||||||
|
|
||||||
match S::init(leaf, packet, &mut init) {
|
match S::init(leaf, packet, &mut init) {
|
||||||
SessionInitResult::Created(state) => {
|
Ok(state) => {
|
||||||
family.entries.push(SessionEntry::new(hook_id, state));
|
family.entries.push(SessionEntry::new(hook_id, path, state));
|
||||||
|
|
||||||
if let Some(store) = interface.as_mut() {
|
if let Some(store) = interface.as_mut() {
|
||||||
store.record_for(
|
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() {
|
if let Some(store) = interface.as_mut() {
|
||||||
store.record_for(
|
store.record_for(
|
||||||
target,
|
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() {
|
if let Some(store) = interface.as_mut() {
|
||||||
store.record_for(
|
store.record_for(
|
||||||
target,
|
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 started_ns = interface.as_ref().and_then(|store| store.now_ns());
|
||||||
let outbox_start = entry.outbox.len();
|
let outbox_start = entry.outbox.len();
|
||||||
let reply_path = S::reply_path(&entry.state).to_vec();
|
let path = entry.path.clone();
|
||||||
let status = {
|
let status = {
|
||||||
let mut ctx = SessionCtx::new(
|
let mut ctx = SessionCtx::new(entry.hook_id, path, S::PROCEDURE_ID, &mut entry.outbox);
|
||||||
entry.hook_id,
|
|
||||||
reply_path,
|
|
||||||
S::PROCEDURE_ID,
|
|
||||||
&mut entry.outbox,
|
|
||||||
);
|
|
||||||
|
|
||||||
S::update(leaf, &mut entry.state, &mut entry.inbox, &mut ctx)
|
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;
|
/// const PROCEDURE_ID: u32 = 7;
|
||||||
/// type State = MySessionState;
|
/// type State = MySessionState;
|
||||||
///
|
///
|
||||||
/// fn reply_path(state: &Self::State) -> &[u32] {
|
|
||||||
/// &state.reply_path
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn init(
|
/// fn init(
|
||||||
/// leaf: &mut MyLeafState,
|
/// leaf: &mut MyLeafState,
|
||||||
/// packet: Packet,
|
/// packet: Packet,
|
||||||
/// ctx: &mut SessionInit,
|
/// ctx: &mut SessionInit,
|
||||||
/// ) -> SessionInitResult<Self::State> {
|
/// ) -> Result<Self::State, SessionInitError> {
|
||||||
/// SessionInitResult::Created(MySessionState::from_open(leaf, packet, ctx))
|
/// Ok(MySessionState::from_open(leaf, packet, ctx))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn update(
|
/// fn update(
|
||||||
@@ -50,20 +46,16 @@ pub trait Session<L> {
|
|||||||
/// Application state stored for one live hook.
|
/// Application state stored for one live hook.
|
||||||
type State;
|
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.
|
/// Creates one session state from a packet whose hook has no active session.
|
||||||
///
|
///
|
||||||
/// Returning [`SessionInitResult::RejectedWith`] lets the generated leaf route a
|
/// The generated runtime derives all response routing from hook state. Session
|
||||||
/// protocol-level failure response with the same retry guarantees as normal
|
/// initialization therefore returns only application state or a protocol-level
|
||||||
/// output. Returning [`SessionInitResult::Rejected`] silently consumes the packet.
|
/// rejection; it never stores or receives a caller reply path.
|
||||||
fn init(leaf: &mut L, packet: Packet, ctx: &mut SessionInit) -> SessionInitResult<Self::State>;
|
fn init(
|
||||||
|
leaf: &mut L,
|
||||||
|
packet: Packet,
|
||||||
|
ctx: &mut SessionInit,
|
||||||
|
) -> Result<Self::State, SessionInitError>;
|
||||||
|
|
||||||
/// Advances one active hook session.
|
/// 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.
|
/// Error returned when a packet cannot create a new session.
|
||||||
pub enum SessionInitResult<S> {
|
pub enum SessionInitError {
|
||||||
/// A new session was created and should be stored by the generated leaf.
|
/// The packet was intentionally consumed without creating state or sending output.
|
||||||
Created(S),
|
|
||||||
|
|
||||||
/// The packet was intentionally consumed without creating state or a response.
|
|
||||||
Rejected,
|
Rejected,
|
||||||
|
|
||||||
/// The packet was rejected with a response that the generated leaf must route.
|
/// The packet was rejected with response data that should be sent on the same hook.
|
||||||
RejectedWith(Packet),
|
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`].
|
/// 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.
|
/// routing in generated code is what makes final-frame retries reliable.
|
||||||
pub struct SessionCtx<'a> {
|
pub struct SessionCtx<'a> {
|
||||||
hook_id: HookID,
|
hook_id: HookID,
|
||||||
reply_path: Vec<u32>,
|
path: Vec<u32>,
|
||||||
procedure_id: u32,
|
procedure_id: u32,
|
||||||
outbox: &'a mut PacketQueue,
|
outbox: &'a mut PacketQueue,
|
||||||
}
|
}
|
||||||
@@ -162,13 +180,13 @@ impl<'a> SessionCtx<'a> {
|
|||||||
/// Creates a context for one session update call.
|
/// Creates a context for one session update call.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
hook_id: HookID,
|
hook_id: HookID,
|
||||||
reply_path: Vec<u32>,
|
path: Vec<u32>,
|
||||||
procedure_id: u32,
|
procedure_id: u32,
|
||||||
outbox: &'a mut PacketQueue,
|
outbox: &'a mut PacketQueue,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
hook_id,
|
hook_id,
|
||||||
reply_path,
|
path,
|
||||||
procedure_id,
|
procedure_id,
|
||||||
outbox,
|
outbox,
|
||||||
}
|
}
|
||||||
@@ -179,11 +197,6 @@ impl<'a> SessionCtx<'a> {
|
|||||||
self.hook_id
|
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.
|
/// Queues a one-byte-opcode frame without closing the hook.
|
||||||
pub fn send(&mut self, opcode: u8, data: &[u8]) {
|
pub fn send(&mut self, opcode: u8, data: &[u8]) {
|
||||||
self.send_frame(opcode, data, false);
|
self.send_frame(opcode, data, false);
|
||||||
@@ -233,7 +246,7 @@ impl<'a> SessionCtx<'a> {
|
|||||||
self.outbox.push_back(Packet {
|
self.outbox.push_back(Packet {
|
||||||
hook_id: self.hook_id,
|
hook_id: self.hook_id,
|
||||||
end_hook,
|
end_hook,
|
||||||
path: self.reply_path.clone(),
|
path: self.path.clone(),
|
||||||
procedure_id: self.procedure_id,
|
procedure_id: self.procedure_id,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
@@ -249,6 +262,13 @@ pub struct SessionEntry<S> {
|
|||||||
/// Hook id associated with this live session.
|
/// Hook id associated with this live session.
|
||||||
pub hook_id: HookID,
|
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.
|
/// Application-owned session state.
|
||||||
pub state: S,
|
pub state: S,
|
||||||
|
|
||||||
@@ -300,9 +320,10 @@ impl<S> Default for SessionFamily<S> {
|
|||||||
|
|
||||||
impl<S> SessionEntry<S> {
|
impl<S> SessionEntry<S> {
|
||||||
/// Creates one active session entry for `hook_id`.
|
/// 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 {
|
Self {
|
||||||
hook_id,
|
hook_id,
|
||||||
|
path,
|
||||||
state,
|
state,
|
||||||
inbox: PacketQueue::new(),
|
inbox: PacketQueue::new(),
|
||||||
outbox: PacketQueue::new(),
|
outbox: PacketQueue::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user