mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Simplify session routing path
This commit is contained in:
@@ -2,7 +2,7 @@ use alloc::vec::Vec;
|
||||
|
||||
use unshell::protocol::{HookID, Packet};
|
||||
|
||||
use crate::{OP_ERROR, OP_OPEN, PROC_PTY};
|
||||
use crate::{OP_OPEN, PROC_PTY};
|
||||
|
||||
/// Encodes a tiny PTY frame into `Packet::data`.
|
||||
pub fn encode_frame(opcode: u8, payload: &[u8]) -> Vec<u8> {
|
||||
@@ -12,35 +12,9 @@ pub fn encode_frame(opcode: u8, payload: &[u8]) -> Vec<u8> {
|
||||
data
|
||||
}
|
||||
|
||||
/// Encodes an `Open` payload with the caller's reply path.
|
||||
pub fn encode_open(reply_path: &[u32]) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(2 + reply_path.len() * 4);
|
||||
data.push(OP_OPEN);
|
||||
data.push(reply_path.len() as u8);
|
||||
|
||||
for segment in reply_path {
|
||||
data.extend_from_slice(&segment.to_le_bytes());
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
/// Decodes the reply path embedded in an `Open` payload after the opcode byte.
|
||||
pub fn decode_open_reply_path(payload: &[u8]) -> Option<Vec<u32>> {
|
||||
let path_len = usize::from(*payload.first()?);
|
||||
let path_bytes = path_len.checked_mul(4)?;
|
||||
let expected_len = 1usize.checked_add(path_bytes)?;
|
||||
|
||||
if payload.len() != expected_len {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut path = Vec::with_capacity(path_len);
|
||||
for chunk in payload[1..].chunks_exact(4) {
|
||||
path.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
|
||||
}
|
||||
|
||||
Some(path)
|
||||
/// Encodes an `Open` frame.
|
||||
pub fn encode_open() -> Vec<u8> {
|
||||
alloc::vec![OP_OPEN]
|
||||
}
|
||||
|
||||
/// Returns the opcode byte from a PTY packet, if present.
|
||||
@@ -74,33 +48,13 @@ pub fn pty_packet(
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an outer PTY open packet with the specialized open payload shape.
|
||||
pub fn pty_open_packet(path: Vec<u32>, hook_id: HookID, reply_path: &[u32]) -> Packet {
|
||||
/// Builds an outer PTY open packet.
|
||||
pub fn pty_open_packet(path: Vec<u32>, hook_id: HookID) -> Packet {
|
||||
Packet {
|
||||
hook_id,
|
||||
end_hook: false,
|
||||
path,
|
||||
procedure_id: PROC_PTY,
|
||||
data: encode_open(reply_path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a final error packet for session initialization failures.
|
||||
pub(crate) fn error_packet(hook_id: HookID, reply_path: Vec<u32>, payload: &[u8]) -> Packet {
|
||||
Packet {
|
||||
hook_id,
|
||||
end_hook: true,
|
||||
path: reply_path,
|
||||
procedure_id: PROC_PTY,
|
||||
data: encode_frame(OP_ERROR, payload),
|
||||
}
|
||||
}
|
||||
|
||||
/// Infers the caller reply path from a locally delivered destination path.
|
||||
pub(crate) fn reply_path_from_destination(destination: &[u32]) -> Vec<u32> {
|
||||
if destination.len() > 1 {
|
||||
destination[..destination.len() - 1].to_vec()
|
||||
} else {
|
||||
destination.to_vec()
|
||||
data: encode_open(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@ mod session;
|
||||
mod state;
|
||||
|
||||
pub use codec::{
|
||||
decode_open_reply_path, encode_frame, encode_open, frame_opcode, frame_payload,
|
||||
pty_open_packet, pty_packet,
|
||||
encode_frame, encode_open, frame_opcode, frame_payload, pty_open_packet, pty_packet,
|
||||
};
|
||||
pub use constants::*;
|
||||
pub use session::{PtySession, PtySessionState};
|
||||
pub use session::PtySessionState;
|
||||
pub use state::{FakePtyLeaf, FakePtyState};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use unshell::protocol::{
|
||||
HookID, Packet, PacketQueue, Session, SessionCtx, SessionInit, SessionInitResult, SessionStatus,
|
||||
Endpoint, HookID, Packet, PacketQueue, Session, SessionInitError, SessionStatus,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
codec::{
|
||||
decode_open_reply_path, error_packet, frame_opcode, frame_payload,
|
||||
reply_path_from_destination,
|
||||
},
|
||||
codec::{encode_frame, frame_opcode, frame_payload},
|
||||
constants::{
|
||||
OP_ABORT, OP_ERROR, OP_EXIT, OP_INPUT, OP_OPEN, OP_OPENED, OP_OUTPUT, OP_STDIN_EOF,
|
||||
OP_TERMINATE, PROC_PTY,
|
||||
@@ -16,51 +11,32 @@ use crate::{
|
||||
state::FakePtyState,
|
||||
};
|
||||
|
||||
/// Session contract for one hook-backed fake PTY.
|
||||
pub struct PtySession;
|
||||
|
||||
/// Per-hook fake PTY session state.
|
||||
///
|
||||
/// A real PTY leaf will replace the pending flags with a worker handle. The reply path
|
||||
/// and hook lifecycle behavior should stay the same.
|
||||
/// A real PTY leaf will replace the pending flags with a worker handle. Hook routing
|
||||
/// is owned by the generated runtime, so this state only tracks PTY behavior.
|
||||
pub struct PtySessionState {
|
||||
hook_id: HookID,
|
||||
reply_path: Vec<u32>,
|
||||
opened_pending: bool,
|
||||
stdin_closed: bool,
|
||||
}
|
||||
|
||||
impl Session<FakePtyState> for PtySession {
|
||||
impl Session<FakePtyState> for PtySessionState {
|
||||
const PROCEDURE_ID: u32 = PROC_PTY;
|
||||
|
||||
type State = PtySessionState;
|
||||
|
||||
fn reply_path(session: &Self::State) -> &[u32] {
|
||||
&session.reply_path
|
||||
}
|
||||
|
||||
fn init(
|
||||
leaf: &mut FakePtyState,
|
||||
packet: Packet,
|
||||
ctx: &mut SessionInit,
|
||||
) -> SessionInitResult<Self::State> {
|
||||
fn init(leaf: &mut FakePtyState, packet: Packet) -> Result<Self, SessionInitError> {
|
||||
if frame_opcode(&packet) != Some(OP_OPEN) {
|
||||
return SessionInitResult::RejectedWith(error_packet(
|
||||
ctx.hook_id(),
|
||||
reply_path_from_destination(ctx.packet_path()),
|
||||
return Err(SessionInitError::response_final(encode_frame(
|
||||
OP_ERROR,
|
||||
b"unknown-session",
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
let reply_path = decode_open_reply_path(frame_payload(&packet))
|
||||
.unwrap_or_else(|| reply_path_from_destination(ctx.packet_path()));
|
||||
|
||||
leaf.active_count += 1;
|
||||
leaf.total_opened += 1;
|
||||
|
||||
SessionInitResult::Created(PtySessionState {
|
||||
hook_id: ctx.hook_id(),
|
||||
reply_path,
|
||||
Ok(Self {
|
||||
hook_id: packet.hook_id,
|
||||
opened_pending: true,
|
||||
stdin_closed: false,
|
||||
})
|
||||
@@ -68,24 +44,44 @@ impl Session<FakePtyState> for PtySession {
|
||||
|
||||
fn update(
|
||||
leaf: &mut FakePtyState,
|
||||
session: &mut Self::State,
|
||||
session: &mut Self,
|
||||
incoming: &mut PacketQueue,
|
||||
ctx: &mut SessionCtx<'_>,
|
||||
endpoint: &mut Endpoint,
|
||||
) -> SessionStatus {
|
||||
if session.opened_pending {
|
||||
ctx.send(OP_OPENED, &[]);
|
||||
let _ = endpoint.send_hook_frame(
|
||||
session.hook_id,
|
||||
Self::PROCEDURE_ID,
|
||||
OP_OPENED,
|
||||
&[],
|
||||
false,
|
||||
);
|
||||
session.opened_pending = false;
|
||||
}
|
||||
|
||||
while let Some(packet) = incoming.pop_front() {
|
||||
match frame_opcode(&packet) {
|
||||
Some(OP_INPUT) => ctx.send(OP_OUTPUT, frame_payload(&packet)),
|
||||
Some(OP_INPUT) => {
|
||||
let _ = endpoint.send_hook_frame(
|
||||
session.hook_id,
|
||||
Self::PROCEDURE_ID,
|
||||
OP_OUTPUT,
|
||||
frame_payload(&packet),
|
||||
false,
|
||||
);
|
||||
}
|
||||
Some(OP_STDIN_EOF) => {
|
||||
session.stdin_closed = true;
|
||||
leaf.last_stdin_eof_hook = Some(session.hook_id);
|
||||
}
|
||||
Some(OP_TERMINATE) => {
|
||||
ctx.send_final(OP_EXIT, &[0]);
|
||||
let _ = endpoint.send_hook_frame(
|
||||
session.hook_id,
|
||||
Self::PROCEDURE_ID,
|
||||
OP_EXIT,
|
||||
&[0],
|
||||
true,
|
||||
);
|
||||
close_session(leaf);
|
||||
return SessionStatus::Closed;
|
||||
}
|
||||
@@ -94,12 +90,24 @@ impl Session<FakePtyState> for PtySession {
|
||||
return SessionStatus::Closed;
|
||||
}
|
||||
Some(OP_OPEN) => {
|
||||
ctx.send_final(OP_ERROR, b"duplicate-open");
|
||||
let _ = endpoint.send_hook_frame(
|
||||
session.hook_id,
|
||||
Self::PROCEDURE_ID,
|
||||
OP_ERROR,
|
||||
b"duplicate-open",
|
||||
true,
|
||||
);
|
||||
close_session(leaf);
|
||||
return SessionStatus::Closed;
|
||||
}
|
||||
_ => {
|
||||
ctx.send_final(OP_ERROR, b"unknown-opcode");
|
||||
let _ = endpoint.send_hook_frame(
|
||||
session.hook_id,
|
||||
Self::PROCEDURE_ID,
|
||||
OP_ERROR,
|
||||
b"unknown-opcode",
|
||||
true,
|
||||
);
|
||||
close_session(leaf);
|
||||
return SessionStatus::Closed;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use unshell::protocol::{HookID, unshell_leaf};
|
||||
|
||||
use crate::{constants::LEAF_FAKE_PTY, procedure::PingProcedure, session::PtySession};
|
||||
use crate::{constants::LEAF_FAKE_PTY, procedure::PingProcedure, session::PtySessionState};
|
||||
|
||||
/// User-owned state for the generated fake PTY leaf.
|
||||
///
|
||||
@@ -45,7 +45,7 @@ unshell_leaf! {
|
||||
authors: unshell::alloc::vec!["ASTATIN3"],
|
||||
},
|
||||
sessions {
|
||||
pty: PtySession,
|
||||
pty: PtySessionState,
|
||||
}
|
||||
procedures {
|
||||
ping: PingProcedure,
|
||||
|
||||
@@ -2,17 +2,16 @@ use alloc::vec;
|
||||
|
||||
use unshell::{
|
||||
interface::{InterfaceEventKind, InterfaceStore, ProcedureKey, SessionKey, SessionViewStatus},
|
||||
protocol::{Leaf, Packet},
|
||||
protocol::{Leaf, Packet, SessionStatus},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
FakePtyLeaf, FakePtyState, OP_EXIT, OP_OPENED, OP_TERMINATE, PROC_PTY, constants::PROC_PING,
|
||||
frame_opcode, pty_open_packet,
|
||||
FakePtyLeaf, FakePtyState, OP_TERMINATE, PROC_PTY, constants::PROC_PING, pty_open_packet,
|
||||
};
|
||||
|
||||
use super::support::{
|
||||
ENDPOINT_A, ENDPOINT_B, assert_frame, drain_parent_packets, drain_parent_pty_packets,
|
||||
pty_endpoints, send_downward_frame, transfer_packets,
|
||||
ENDPOINT_A, ENDPOINT_B, drain_parent_packets, drain_parent_pty_packets, pty_endpoints,
|
||||
send_downward_frame, transfer_packets,
|
||||
};
|
||||
|
||||
fn view_has_event<F>(interface: &InterfaceStore, event_indexes: &[usize], mut predicate: F) -> bool
|
||||
@@ -51,11 +50,7 @@ fn interface_update_records_session_flow() {
|
||||
let hook_id = endpoint_a.get_hook_id();
|
||||
|
||||
endpoint_a
|
||||
.add_outbound(pty_open_packet(
|
||||
vec![ENDPOINT_A, ENDPOINT_B],
|
||||
hook_id,
|
||||
&[ENDPOINT_A],
|
||||
))
|
||||
.add_outbound(pty_open_packet(vec![ENDPOINT_A, ENDPOINT_B], hook_id))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
|
||||
@@ -83,34 +78,21 @@ fn interface_update_records_session_flow() {
|
||||
&session_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::OutboundQueued { packet }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_OPENED)
|
||||
),
|
||||
));
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&session_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::RouteSuccess { packet }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_OPENED)
|
||||
InterfaceEventKind::SessionUpdated { hook_id: recorded_hook, status, .. }
|
||||
if *recorded_hook == hook_id && *status == SessionStatus::Running
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_update_records_failed_final_route_without_dropping_session() {
|
||||
fn interface_update_records_failed_direct_route_without_retry() {
|
||||
let (mut endpoint_a, mut endpoint_b) = pty_endpoints();
|
||||
let mut leaf = FakePtyLeaf::new(FakePtyState::new());
|
||||
let mut interface = InterfaceStore::new();
|
||||
let hook_id = endpoint_a.get_hook_id();
|
||||
|
||||
endpoint_a
|
||||
.add_outbound(pty_open_packet(
|
||||
vec![ENDPOINT_A, ENDPOINT_B],
|
||||
hook_id,
|
||||
&[ENDPOINT_A],
|
||||
))
|
||||
.add_outbound(pty_open_packet(vec![ENDPOINT_A, ENDPOINT_B], hook_id))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
@@ -135,18 +117,9 @@ fn interface_update_records_failed_final_route_without_dropping_session() {
|
||||
};
|
||||
let session_view = interface.session_views().get(&session_key).unwrap();
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 1);
|
||||
assert_eq!(leaf.pending_packet_count(), 1);
|
||||
assert_eq!(leaf.active_session_count(), 0);
|
||||
assert_eq!(leaf.pending_packet_count(), 0);
|
||||
assert_eq!(session_view.status, SessionViewStatus::Closed);
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&session_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::RouteFailure { packet, .. }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_EXIT)
|
||||
),
|
||||
));
|
||||
|
||||
endpoint_b.connections.insert((ENDPOINT_A, true));
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
@@ -156,17 +129,8 @@ fn interface_update_records_failed_final_route_without_dropping_session() {
|
||||
let session_view = interface.session_views().get(&session_key).unwrap();
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 0);
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_frame(&packets[0], hook_id, OP_EXIT, true, &[0]);
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&session_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::RouteSuccess { packet }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_EXIT)
|
||||
),
|
||||
));
|
||||
assert!(packets.is_empty());
|
||||
assert_eq!(session_view.status, SessionViewStatus::Closed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -124,7 +124,7 @@ fn exit_end_hook_cleans_route_and_session() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_final_exit_route_retries_without_losing_session() {
|
||||
fn failed_final_exit_route_closes_session_without_retry() {
|
||||
let (mut endpoint_a, mut endpoint_b) = pty_endpoints();
|
||||
let mut leaf = FakePtyLeaf::new(FakePtyState::new());
|
||||
let hook_id = open_pty_session(&mut endpoint_a, &mut endpoint_b, &mut leaf);
|
||||
@@ -141,19 +141,18 @@ fn failed_final_exit_route_retries_without_losing_session() {
|
||||
endpoint_b.connections.remove(&(ENDPOINT_A, true));
|
||||
leaf.update(&mut endpoint_b);
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 1);
|
||||
assert_eq!(leaf.pending_packet_count(), 1);
|
||||
assert_hook_present(&endpoint_b, hook_id);
|
||||
assert_eq!(leaf.active_session_count(), 0);
|
||||
assert_eq!(leaf.pending_packet_count(), 0);
|
||||
assert_hook_removed(&endpoint_b, hook_id);
|
||||
|
||||
endpoint_b.connections.insert((ENDPOINT_A, true));
|
||||
leaf.update(&mut endpoint_b);
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
let packets = drain_parent_pty_packets(&mut endpoint_a);
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_frame(&packets[0], hook_id, OP_EXIT, true, &[0]);
|
||||
assert!(packets.is_empty());
|
||||
assert_eq!(leaf.active_session_count(), 0);
|
||||
assert_hook_removed(&endpoint_a, hook_id);
|
||||
assert_hook_present(&endpoint_a, hook_id);
|
||||
assert_hook_removed(&endpoint_b, hook_id);
|
||||
}
|
||||
|
||||
@@ -252,10 +251,7 @@ fn pty_leaf_does_not_consume_other_leaf_packets() {
|
||||
endpoint.connections.insert((ENDPOINT_A, true));
|
||||
|
||||
endpoint
|
||||
.add_inbound_from(
|
||||
ENDPOINT_A,
|
||||
pty_open_packet(vec![ENDPOINT_A, ENDPOINT_B], 7, &[ENDPOINT_A]),
|
||||
)
|
||||
.add_inbound_from(ENDPOINT_A, pty_open_packet(vec![ENDPOINT_A, ENDPOINT_B], 7))
|
||||
.unwrap();
|
||||
endpoint
|
||||
.add_inbound_from(
|
||||
|
||||
@@ -72,11 +72,7 @@ pub(super) fn open_pty_session(
|
||||
) -> u16 {
|
||||
let hook_id = endpoint_a.get_hook_id();
|
||||
endpoint_a
|
||||
.add_outbound(pty_open_packet(
|
||||
vec![ENDPOINT_A, ENDPOINT_B],
|
||||
hook_id,
|
||||
&[ENDPOINT_A],
|
||||
))
|
||||
.add_outbound(pty_open_packet(vec![ENDPOINT_A, ENDPOINT_B], hook_id))
|
||||
.unwrap();
|
||||
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
|
||||
Reference in New Issue
Block a user