2026-04-25 16:27:10 -06:00
|
|
|
use std::io::{self, ErrorKind, Read, Write};
|
|
|
|
|
use std::net::TcpStream;
|
|
|
|
|
use std::sync::mpsc::{self, Receiver};
|
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
|
|
use unshell::protocol::FrameBytes;
|
|
|
|
|
use unshell::protocol::tree::EndpointOutcome;
|
|
|
|
|
|
2026-04-26 12:39:06 -06:00
|
|
|
/// TCP listen address used by the remote shell examples.
|
2026-04-25 16:27:10 -06:00
|
|
|
pub const LISTEN_ADDR: &str = "127.0.0.1:4444";
|
2026-04-25 17:42:39 -06:00
|
|
|
const MAX_FRAME_BYTES: usize = 1024 * 1024;
|
2026-04-25 16:27:10 -06:00
|
|
|
|
2026-04-26 12:39:06 -06:00
|
|
|
/// Writes the forwarded frame produced by one endpoint outcome.
|
2026-04-25 16:27:10 -06:00
|
|
|
pub fn send_forward(stream: &mut TcpStream, outcome: EndpointOutcome) -> io::Result<()> {
|
2026-04-25 20:47:37 -06:00
|
|
|
match outcome {
|
|
|
|
|
EndpointOutcome::Forward { frame, .. } => write_frames(stream, &[frame]),
|
|
|
|
|
EndpointOutcome::Local(_) | EndpointOutcome::Dropped => write_frames(stream, &[]),
|
|
|
|
|
}
|
2026-04-25 16:27:10 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-26 12:39:06 -06:00
|
|
|
/// Writes one or more framed packets onto the example TCP stream.
|
2026-04-25 16:27:10 -06:00
|
|
|
pub fn write_frames(stream: &mut TcpStream, frames: &[FrameBytes]) -> io::Result<()> {
|
|
|
|
|
for frame in frames {
|
|
|
|
|
let frame_len = u32::try_from(frame.len()).map_err(|_| {
|
|
|
|
|
io::Error::new(ErrorKind::InvalidData, "frame exceeds u32 transport size")
|
|
|
|
|
})?;
|
|
|
|
|
stream.write_all(&frame_len.to_be_bytes())?;
|
|
|
|
|
stream.write_all(frame)?;
|
|
|
|
|
}
|
|
|
|
|
stream.flush()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 12:39:06 -06:00
|
|
|
/// Spawns the example frame reader that lifts prefixed frames off the TCP stream.
|
2026-04-25 16:27:10 -06:00
|
|
|
pub fn spawn_frame_reader(mut stream: TcpStream) -> Receiver<io::Result<FrameBytes>> {
|
2026-04-25 17:42:39 -06:00
|
|
|
let (tx, rx) = mpsc::sync_channel(64);
|
2026-04-25 16:27:10 -06:00
|
|
|
|
|
|
|
|
thread::spawn(move || {
|
|
|
|
|
loop {
|
|
|
|
|
match read_frame(&mut stream) {
|
|
|
|
|
Ok(Some(frame)) => {
|
|
|
|
|
if tx.send(Ok(frame)).is_err() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => break,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
let _ = tx.send(Err(error));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_frame(stream: &mut TcpStream) -> io::Result<Option<FrameBytes>> {
|
2026-04-25 17:42:39 -06:00
|
|
|
let Some(len_bytes) = read_prefix(stream)? else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
2026-04-25 16:27:10 -06:00
|
|
|
|
|
|
|
|
let frame_len = u32::from_be_bytes(len_bytes) as usize;
|
2026-04-25 17:42:39 -06:00
|
|
|
if frame_len > MAX_FRAME_BYTES {
|
|
|
|
|
return Err(io::Error::new(
|
|
|
|
|
ErrorKind::InvalidData,
|
|
|
|
|
"frame exceeds remote shell example transport limit",
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-04-25 16:27:10 -06:00
|
|
|
let mut bytes = vec![0u8; frame_len];
|
2026-04-26 12:39:06 -06:00
|
|
|
stream.read_exact(&mut bytes)?;
|
2026-04-25 16:27:10 -06:00
|
|
|
|
|
|
|
|
let mut frame = FrameBytes::with_capacity(bytes.len());
|
|
|
|
|
frame.extend_from_slice(&bytes);
|
|
|
|
|
Ok(Some(frame))
|
|
|
|
|
}
|
2026-04-25 17:42:39 -06:00
|
|
|
|
|
|
|
|
fn read_prefix(stream: &mut TcpStream) -> io::Result<Option<[u8; 4]>> {
|
|
|
|
|
let mut len_bytes = [0u8; 4];
|
|
|
|
|
let mut filled = 0usize;
|
|
|
|
|
|
|
|
|
|
while filled < len_bytes.len() {
|
|
|
|
|
match stream.read(&mut len_bytes[filled..]) {
|
|
|
|
|
Ok(0) if filled == 0 => return Ok(None),
|
|
|
|
|
Ok(0) => return Err(io::Error::from(ErrorKind::UnexpectedEof)),
|
|
|
|
|
Ok(read_len) => filled += read_len,
|
|
|
|
|
Err(error) if error.kind() == ErrorKind::Interrupted => {}
|
|
|
|
|
Err(error) => return Err(error),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Some(len_bytes))
|
|
|
|
|
}
|