mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Redesign interface event ownership.
This commit is contained in:
@@ -6,6 +6,9 @@ pub const LEAF_FAKE_PTY: u32 = hash_32!("dev.unshell.v1.pty");
|
||||
/// Outer procedure id used by all fake PTY session packets.
|
||||
pub const PROC_PTY: u32 = hash_32!("dev.unshell.v1.pty.pty");
|
||||
|
||||
/// One-shot procedure id used by tests to prove procedure interface ownership.
|
||||
pub(crate) const PROC_PING: u32 = hash_32!("dev.unshell.v1.pty.ping");
|
||||
|
||||
/// Downward opcode that opens one PTY session.
|
||||
pub const OP_OPEN: u8 = 0;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ extern crate alloc;
|
||||
|
||||
mod codec;
|
||||
mod constants;
|
||||
mod procedure;
|
||||
mod session;
|
||||
mod state;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use unshell::protocol::{Endpoint, Packet, Procedure, ProcedureOut};
|
||||
|
||||
use crate::{constants::PROC_PING, state::FakePtyState};
|
||||
|
||||
/// One-shot echo procedure used to exercise generated procedure dispatch.
|
||||
///
|
||||
/// The fake PTY leaf is primarily session-oriented, so this deliberately small
|
||||
/// procedure gives tests a non-session packet family. That keeps interface logging
|
||||
/// honest: procedure packets should populate [`unshell::interface::ProcedureView`]
|
||||
/// instead of being inferred as hook-backed sessions.
|
||||
pub(crate) struct PingProcedure;
|
||||
|
||||
impl Procedure<FakePtyState> for PingProcedure {
|
||||
const PROCEDURE_ID: u32 = PROC_PING;
|
||||
|
||||
fn handle(_: &mut FakePtyState, _: &mut Endpoint, packet: Packet, out: &mut ProcedureOut) {
|
||||
out.send_final(&packet.data);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use unshell::protocol::{HookID, unshell_leaf};
|
||||
|
||||
use crate::{constants::LEAF_FAKE_PTY, session::PtySession};
|
||||
use crate::{constants::LEAF_FAKE_PTY, procedure::PingProcedure, session::PtySession};
|
||||
|
||||
/// User-owned state for the generated fake PTY leaf.
|
||||
///
|
||||
@@ -47,6 +47,8 @@ unshell_leaf! {
|
||||
sessions {
|
||||
pty: PtySession,
|
||||
}
|
||||
procedures {}
|
||||
procedures {
|
||||
ping: PingProcedure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use alloc::vec;
|
||||
|
||||
use unshell::{
|
||||
interface::{InterfaceEventKind, InterfaceStore, ProcedureKey, SessionKey, SessionViewStatus},
|
||||
protocol::{Leaf, Packet},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
FakePtyLeaf, FakePtyState, OP_EXIT, OP_OPENED, OP_TERMINATE, PROC_PTY, constants::PROC_PING,
|
||||
frame_opcode, 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,
|
||||
};
|
||||
|
||||
fn view_has_event<F>(interface: &InterfaceStore, event_indexes: &[usize], mut predicate: F) -> bool
|
||||
where
|
||||
F: FnMut(&InterfaceEventKind) -> bool,
|
||||
{
|
||||
event_indexes
|
||||
.iter()
|
||||
.any(|index| predicate(&interface.events()[*index].kind))
|
||||
}
|
||||
|
||||
fn send_downward_ping(
|
||||
endpoint_a: &mut unshell::protocol::Endpoint,
|
||||
endpoint_b: &mut unshell::protocol::Endpoint,
|
||||
hook_id: u16,
|
||||
payload: &[u8],
|
||||
) {
|
||||
endpoint_a
|
||||
.add_outbound(Packet {
|
||||
hook_id,
|
||||
end_hook: false,
|
||||
path: vec![ENDPOINT_A, ENDPOINT_B],
|
||||
procedure_id: PROC_PING,
|
||||
data: payload.to_vec(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_update_records_session_flow() {
|
||||
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],
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
|
||||
let session_key = SessionKey {
|
||||
leaf_id: leaf.get_id(),
|
||||
procedure_id: PROC_PTY,
|
||||
hook_id,
|
||||
};
|
||||
let session_view = interface.session_views().get(&session_key).unwrap();
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 1);
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&session_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::SessionCreated { hook_id: recorded_hook, .. }
|
||||
if *recorded_hook == hook_id
|
||||
),
|
||||
));
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&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)
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_update_records_failed_final_route_without_dropping_session() {
|
||||
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],
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
drain_parent_pty_packets(&mut endpoint_a);
|
||||
|
||||
send_downward_frame(
|
||||
&mut endpoint_a,
|
||||
&mut endpoint_b,
|
||||
hook_id,
|
||||
OP_TERMINATE,
|
||||
&[],
|
||||
false,
|
||||
);
|
||||
endpoint_b.connections.remove(&(ENDPOINT_A, true));
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
|
||||
let session_key = SessionKey {
|
||||
leaf_id: leaf.get_id(),
|
||||
procedure_id: PROC_PTY,
|
||||
hook_id,
|
||||
};
|
||||
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!(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);
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
let packets = drain_parent_pty_packets(&mut endpoint_a);
|
||||
|
||||
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)
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_update_records_procedure_flow_without_session_view() {
|
||||
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();
|
||||
|
||||
send_downward_ping(&mut endpoint_a, &mut endpoint_b, hook_id, b"ping");
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
|
||||
let leaf_id = leaf.get_id();
|
||||
let procedure_key = ProcedureKey {
|
||||
leaf_id,
|
||||
procedure_id: PROC_PING,
|
||||
};
|
||||
let session_key = SessionKey {
|
||||
leaf_id,
|
||||
procedure_id: PROC_PING,
|
||||
hook_id,
|
||||
};
|
||||
let procedure_view = interface.procedure_views().get(&procedure_key).unwrap();
|
||||
|
||||
assert!(!interface.session_views().contains_key(&session_key));
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&procedure_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::Inbound { packet }
|
||||
if packet.hook_id == hook_id && packet.procedure_id == PROC_PING
|
||||
),
|
||||
));
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&procedure_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::ProcedureCalled { procedure_id, hook_id: recorded_hook, .. }
|
||||
if *procedure_id == PROC_PING && *recorded_hook == hook_id
|
||||
),
|
||||
));
|
||||
assert!(view_has_event(
|
||||
&interface,
|
||||
&procedure_view.events,
|
||||
|event| matches!(
|
||||
event,
|
||||
InterfaceEventKind::RouteSuccess { packet }
|
||||
if packet.hook_id == hook_id && packet.procedure_id == PROC_PING
|
||||
),
|
||||
));
|
||||
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
let packets = drain_parent_packets(&mut endpoint_a, PROC_PING);
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_eq!(packets[0].hook_id, hook_id);
|
||||
assert!(packets[0].end_hook);
|
||||
assert_eq!(packets[0].data, b"ping".to_vec());
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod session;
|
||||
mod support;
|
||||
|
||||
#[cfg(feature = "interface")]
|
||||
mod interface;
|
||||
+10
-224
@@ -1,127 +1,17 @@
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use unshell::protocol::{Endpoint, Leaf, Packet};
|
||||
use unshell::protocol::{Leaf, Packet};
|
||||
|
||||
#[cfg(feature = "interface")]
|
||||
use unshell::interface::{InterfaceEventKind, InterfaceStore, SessionKey, SessionViewStatus};
|
||||
|
||||
use super::{
|
||||
FakePtyLeaf, FakePtyState, OP_ABORT, OP_ERROR, OP_EXIT, OP_INPUT, OP_OPENED, OP_OUTPUT,
|
||||
OP_STDIN_EOF, OP_TERMINATE, PROC_PTY, frame_opcode, frame_payload, pty_open_packet, pty_packet,
|
||||
use crate::{
|
||||
FakePtyLeaf, FakePtyState, OP_ABORT, OP_ERROR, OP_EXIT, OP_INPUT, OP_OUTPUT, OP_STDIN_EOF,
|
||||
OP_TERMINATE, pty_open_packet,
|
||||
};
|
||||
|
||||
const ENDPOINT_A: u32 = 0;
|
||||
const ENDPOINT_B: u32 = 1;
|
||||
const PROC_OTHER: u32 = 31;
|
||||
|
||||
/// Creates a bare endpoint at a known absolute path.
|
||||
fn endpoint_at(id: u32, path: Vec<u32>) -> Endpoint {
|
||||
let mut endpoint = Endpoint::new(id, vec![]);
|
||||
endpoint.path = path;
|
||||
endpoint
|
||||
}
|
||||
|
||||
/// Creates the parent/child endpoint pair used by PTY session tests.
|
||||
fn pty_endpoints() -> (Endpoint, Endpoint) {
|
||||
let mut endpoint_a = endpoint_at(ENDPOINT_A, vec![ENDPOINT_A]);
|
||||
let mut endpoint_b = endpoint_at(ENDPOINT_B, vec![ENDPOINT_A, ENDPOINT_B]);
|
||||
|
||||
endpoint_a.connections.insert((ENDPOINT_B, false));
|
||||
endpoint_b.connections.insert((ENDPOINT_A, true));
|
||||
|
||||
(endpoint_a, endpoint_b)
|
||||
}
|
||||
|
||||
/// Transfers every queued packet for `next_hop` into `receiver` as `remote_id` traffic.
|
||||
fn transfer_packets(sender: &mut Endpoint, receiver: &mut Endpoint, next_hop: u32, remote_id: u32) {
|
||||
let mut packets = Vec::new();
|
||||
sender.take_outbound_clear(next_hop, |packet| packets.push(packet.clone()));
|
||||
|
||||
for packet in packets {
|
||||
receiver.add_inbound_from(remote_id, packet).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends one downward PTY frame from endpoint A to endpoint B.
|
||||
fn send_downward_frame(
|
||||
endpoint_a: &mut Endpoint,
|
||||
endpoint_b: &mut Endpoint,
|
||||
hook_id: u16,
|
||||
opcode: u8,
|
||||
payload: &[u8],
|
||||
end_hook: bool,
|
||||
) {
|
||||
endpoint_a
|
||||
.add_outbound(pty_packet(
|
||||
vec![ENDPOINT_A, ENDPOINT_B],
|
||||
hook_id,
|
||||
end_hook,
|
||||
opcode,
|
||||
payload,
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
}
|
||||
|
||||
/// Opens a fake PTY session and delivers the `Opened` response to endpoint A.
|
||||
fn open_pty_session(
|
||||
endpoint_a: &mut Endpoint,
|
||||
endpoint_b: &mut Endpoint,
|
||||
leaf: &mut FakePtyLeaf,
|
||||
) -> 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],
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
leaf.update(endpoint_b);
|
||||
transfer_packets(endpoint_b, endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
|
||||
hook_id
|
||||
}
|
||||
|
||||
/// Drains PTY packets delivered to endpoint A.
|
||||
fn drain_parent_pty_packets(endpoint: &mut Endpoint) -> Vec<Packet> {
|
||||
let mut packets = Vec::new();
|
||||
endpoint.take_inbound_matching(
|
||||
ENDPOINT_A,
|
||||
|packet| packet.procedure_id == PROC_PTY,
|
||||
|packet| packets.push(packet),
|
||||
);
|
||||
packets
|
||||
}
|
||||
|
||||
/// Asserts that local hook state still contains `hook_id`.
|
||||
fn assert_hook_present(endpoint: &Endpoint, hook_id: u16) {
|
||||
assert!(endpoint.has_hook(hook_id));
|
||||
}
|
||||
|
||||
/// Asserts that local hook state no longer contains `hook_id`.
|
||||
fn assert_hook_removed(endpoint: &Endpoint, hook_id: u16) {
|
||||
assert!(!endpoint.has_hook(hook_id));
|
||||
}
|
||||
|
||||
/// Asserts that `packet` carries the expected PTY frame.
|
||||
fn assert_frame(packet: &Packet, hook_id: u16, opcode: u8, end_hook: bool, payload: &[u8]) {
|
||||
assert_eq!(packet.hook_id, hook_id);
|
||||
assert_eq!(packet.end_hook, end_hook);
|
||||
assert_eq!(frame_opcode(packet), Some(opcode));
|
||||
assert_eq!(frame_payload(packet), payload);
|
||||
}
|
||||
|
||||
/// Returns true when `packets` contains the requested frame.
|
||||
fn has_frame(packets: &[Packet], hook_id: u16, opcode: u8, payload: &[u8]) -> bool {
|
||||
packets.iter().any(|packet| {
|
||||
packet.hook_id == hook_id
|
||||
&& frame_opcode(packet) == Some(opcode)
|
||||
&& frame_payload(packet) == payload
|
||||
})
|
||||
}
|
||||
use super::support::{
|
||||
ENDPOINT_A, ENDPOINT_B, PROC_OTHER, assert_frame, assert_hook_present, assert_hook_removed,
|
||||
assert_opened, drain_parent_pty_packets, endpoint_at, has_frame, open_pty_session,
|
||||
pty_endpoints, send_downward_frame, transfer_packets,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn open_pty_paves_hook_and_creates_session() {
|
||||
@@ -137,7 +27,7 @@ fn open_pty_paves_hook_and_creates_session() {
|
||||
assert_hook_present(&endpoint_a, hook_id);
|
||||
assert_hook_present(&endpoint_b, hook_id);
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_frame(&packets[0], hook_id, OP_OPENED, false, &[]);
|
||||
assert_opened(&packets[0], hook_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -394,107 +284,3 @@ fn pty_leaf_does_not_consume_other_leaf_packets() {
|
||||
assert_eq!(other_packets[0].procedure_id, PROC_OTHER);
|
||||
assert_eq!(other_packets[0].data, b"leave-me".to_vec());
|
||||
}
|
||||
|
||||
#[cfg(feature = "interface")]
|
||||
#[test]
|
||||
fn interface_update_records_session_flow() {
|
||||
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],
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 1);
|
||||
assert!(interface.events().iter().any(|event| {
|
||||
matches!(
|
||||
&event.kind,
|
||||
InterfaceEventKind::SessionCreated { hook_id: recorded_hook, .. }
|
||||
if *recorded_hook == hook_id
|
||||
)
|
||||
}));
|
||||
assert!(interface.events().iter().any(|event| {
|
||||
matches!(
|
||||
&event.kind,
|
||||
InterfaceEventKind::RouteSuccess { packet }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_OPENED)
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(feature = "interface")]
|
||||
#[test]
|
||||
fn interface_update_records_failed_final_route_without_dropping_session() {
|
||||
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],
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(&mut endpoint_a, &mut endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
drain_parent_pty_packets(&mut endpoint_a);
|
||||
|
||||
send_downward_frame(
|
||||
&mut endpoint_a,
|
||||
&mut endpoint_b,
|
||||
hook_id,
|
||||
OP_TERMINATE,
|
||||
&[],
|
||||
false,
|
||||
);
|
||||
endpoint_b.connections.remove(&(ENDPOINT_A, true));
|
||||
leaf.update_interface(&mut endpoint_b, &mut interface);
|
||||
|
||||
let session_key = SessionKey {
|
||||
leaf_id: leaf.get_id(),
|
||||
procedure_id: PROC_PTY,
|
||||
hook_id,
|
||||
};
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 1);
|
||||
assert_eq!(leaf.pending_packet_count(), 1);
|
||||
assert_eq!(
|
||||
interface.session_views().get(&session_key).unwrap().status,
|
||||
SessionViewStatus::Closed
|
||||
);
|
||||
assert!(interface.events().iter().any(|event| {
|
||||
matches!(
|
||||
&event.kind,
|
||||
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);
|
||||
transfer_packets(&mut endpoint_b, &mut endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
let packets = drain_parent_pty_packets(&mut endpoint_a);
|
||||
|
||||
assert_eq!(leaf.active_session_count(), 0);
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_frame(&packets[0], hook_id, OP_EXIT, true, &[0]);
|
||||
assert!(interface.events().iter().any(|event| {
|
||||
matches!(
|
||||
&event.kind,
|
||||
InterfaceEventKind::RouteSuccess { packet }
|
||||
if packet.hook_id == hook_id && frame_opcode(packet) == Some(OP_EXIT)
|
||||
)
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use unshell::protocol::{Endpoint, Leaf, Packet};
|
||||
|
||||
use crate::{
|
||||
FakePtyLeaf, OP_OPENED, PROC_PTY, frame_opcode, frame_payload, pty_open_packet, pty_packet,
|
||||
};
|
||||
|
||||
pub(super) const ENDPOINT_A: u32 = 0;
|
||||
pub(super) const ENDPOINT_B: u32 = 1;
|
||||
pub(super) const PROC_OTHER: u32 = 31;
|
||||
|
||||
/// Creates a bare endpoint at a known absolute path.
|
||||
pub(super) fn endpoint_at(id: u32, path: Vec<u32>) -> Endpoint {
|
||||
let mut endpoint = Endpoint::new(id, vec![]);
|
||||
endpoint.path = path;
|
||||
endpoint
|
||||
}
|
||||
|
||||
/// Creates the parent/child endpoint pair used by PTY session tests.
|
||||
pub(super) fn pty_endpoints() -> (Endpoint, Endpoint) {
|
||||
let mut endpoint_a = endpoint_at(ENDPOINT_A, vec![ENDPOINT_A]);
|
||||
let mut endpoint_b = endpoint_at(ENDPOINT_B, vec![ENDPOINT_A, ENDPOINT_B]);
|
||||
|
||||
endpoint_a.connections.insert((ENDPOINT_B, false));
|
||||
endpoint_b.connections.insert((ENDPOINT_A, true));
|
||||
|
||||
(endpoint_a, endpoint_b)
|
||||
}
|
||||
|
||||
/// Transfers every queued packet for `next_hop` into `receiver` as `remote_id` traffic.
|
||||
pub(super) fn transfer_packets(
|
||||
sender: &mut Endpoint,
|
||||
receiver: &mut Endpoint,
|
||||
next_hop: u32,
|
||||
remote_id: u32,
|
||||
) {
|
||||
let mut packets = Vec::new();
|
||||
sender.take_outbound_clear(next_hop, |packet| packets.push(packet.clone()));
|
||||
|
||||
for packet in packets {
|
||||
receiver.add_inbound_from(remote_id, packet).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends one downward PTY frame from endpoint A to endpoint B.
|
||||
pub(super) fn send_downward_frame(
|
||||
endpoint_a: &mut Endpoint,
|
||||
endpoint_b: &mut Endpoint,
|
||||
hook_id: u16,
|
||||
opcode: u8,
|
||||
payload: &[u8],
|
||||
end_hook: bool,
|
||||
) {
|
||||
endpoint_a
|
||||
.add_outbound(pty_packet(
|
||||
vec![ENDPOINT_A, ENDPOINT_B],
|
||||
hook_id,
|
||||
end_hook,
|
||||
opcode,
|
||||
payload,
|
||||
))
|
||||
.unwrap();
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
}
|
||||
|
||||
/// Opens a fake PTY session and delivers the `Opened` response to endpoint A.
|
||||
pub(super) fn open_pty_session(
|
||||
endpoint_a: &mut Endpoint,
|
||||
endpoint_b: &mut Endpoint,
|
||||
leaf: &mut FakePtyLeaf,
|
||||
) -> 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],
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
transfer_packets(endpoint_a, endpoint_b, ENDPOINT_B, ENDPOINT_A);
|
||||
leaf.update(endpoint_b);
|
||||
transfer_packets(endpoint_b, endpoint_a, ENDPOINT_A, ENDPOINT_B);
|
||||
|
||||
hook_id
|
||||
}
|
||||
|
||||
/// Drains packets for `procedure_id` delivered to endpoint A.
|
||||
pub(super) fn drain_parent_packets(endpoint: &mut Endpoint, procedure_id: u32) -> Vec<Packet> {
|
||||
let mut packets = Vec::new();
|
||||
endpoint.take_inbound_matching(
|
||||
ENDPOINT_A,
|
||||
|packet| packet.procedure_id == procedure_id,
|
||||
|packet| packets.push(packet),
|
||||
);
|
||||
packets
|
||||
}
|
||||
|
||||
/// Drains PTY packets delivered to endpoint A.
|
||||
pub(super) fn drain_parent_pty_packets(endpoint: &mut Endpoint) -> Vec<Packet> {
|
||||
drain_parent_packets(endpoint, PROC_PTY)
|
||||
}
|
||||
|
||||
/// Asserts that local hook state still contains `hook_id`.
|
||||
pub(super) fn assert_hook_present(endpoint: &Endpoint, hook_id: u16) {
|
||||
assert!(endpoint.has_hook(hook_id));
|
||||
}
|
||||
|
||||
/// Asserts that local hook state no longer contains `hook_id`.
|
||||
pub(super) fn assert_hook_removed(endpoint: &Endpoint, hook_id: u16) {
|
||||
assert!(!endpoint.has_hook(hook_id));
|
||||
}
|
||||
|
||||
/// Asserts that `packet` carries the expected PTY frame.
|
||||
pub(super) fn assert_frame(
|
||||
packet: &Packet,
|
||||
hook_id: u16,
|
||||
opcode: u8,
|
||||
end_hook: bool,
|
||||
payload: &[u8],
|
||||
) {
|
||||
assert_eq!(packet.hook_id, hook_id);
|
||||
assert_eq!(packet.end_hook, end_hook);
|
||||
assert_eq!(frame_opcode(packet), Some(opcode));
|
||||
assert_eq!(frame_payload(packet), payload);
|
||||
}
|
||||
|
||||
/// Returns true when `packets` contains the requested frame.
|
||||
pub(super) fn has_frame(packets: &[Packet], hook_id: u16, opcode: u8, payload: &[u8]) -> bool {
|
||||
packets.iter().any(|packet| {
|
||||
packet.hook_id == hook_id
|
||||
&& frame_opcode(packet) == Some(opcode)
|
||||
&& frame_payload(packet) == payload
|
||||
})
|
||||
}
|
||||
|
||||
/// Asserts that a packet is the fake PTY open acknowledgement.
|
||||
pub(super) fn assert_opened(packet: &Packet, hook_id: u16) {
|
||||
assert_frame(packet, hook_id, OP_OPENED, false, &[]);
|
||||
}
|
||||
Reference in New Issue
Block a user