#![cfg(feature = "std")]
use std::collections::{BTreeMap, VecDeque};
use std::io::{Read, Write};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use crate::channel::{ChannelEvent, ChannelOpen, ChannelRequest};
use crate::client::{Client, SplitReader, Transport, io_err};
use crate::error::{Error, Result};
use crate::sftp::{SftpClient, SftpError};
const WAIT_TIMEOUT: Duration = Duration::from_millis(500);
const PUMP_POLL: Duration = Duration::from_millis(50);
fn lock_or_poison<'a>(m: &'a Mutex<Inner>) -> Result<std::sync::MutexGuard<'a, Inner>> {
m.lock()
.map_err(|_| Error::Protocol("SharedClient mutex poisoned"))
}
fn lock_or_poison_io<'a>(m: &'a Mutex<Inner>) -> std::io::Result<std::sync::MutexGuard<'a, Inner>> {
m.lock().map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})
}
type SftpResult<T> = core::result::Result<T, SftpError>;
const MAX_OPEN_ITER: usize = 1_000_000;
#[derive(Default)]
struct ChannelQueue {
data: VecDeque<u8>,
stderr: VecDeque<u8>,
remote_eof: bool,
remote_close: bool,
exit_status: Option<i32>,
exit_signal: Option<String>,
local_eof_sent: bool,
datagram: bool,
packets: VecDeque<Vec<u8>>,
}
struct Inner {
client: Client,
queues: BTreeMap<u32, ChannelQueue>,
notifiers: BTreeMap<u32, Arc<Condvar>>,
pumping: bool,
handlers: Option<IncomingHandlers>,
self_ref: Option<SelfRef>,
last_rx: std::time::Instant,
disconnect: Option<(u32, alloc::string::String)>,
pending_keepalives: u32,
}
struct SelfRef {
inner: std::sync::Weak<Mutex<Inner>>,
lock_waiters: Arc<core::sync::atomic::AtomicUsize>,
reader: Option<Arc<SplitReader>>,
}
#[derive(Clone)]
struct IncomingHandlers {
forwarded_tcpip: Option<Arc<crate::client::ForwardedTcpipCallback>>,
forwarded_streamlocal: Option<Arc<crate::client::ForwardedStreamlocalCallback>>,
auth_agent: Option<Arc<crate::client::AuthAgentCallback>>,
x11: Option<Arc<crate::client::X11Callback>>,
}
#[derive(Clone)]
pub struct SharedClient {
inner: Arc<Mutex<Inner>>,
lock_waiters: Arc<core::sync::atomic::AtomicUsize>,
reader: Option<Arc<SplitReader>>,
}
impl From<Client> for SharedClient {
fn from(mut client: Client) -> Self {
let lock_waiters = Arc::new(core::sync::atomic::AtomicUsize::new(0));
let waiters = lock_waiters.clone();
let reader = client.split_reader();
let reader_ref = reader.clone();
let inner = Arc::new_cyclic(|weak| {
Mutex::new(Inner {
client,
queues: BTreeMap::new(),
notifiers: BTreeMap::new(),
pumping: false,
handlers: None,
last_rx: std::time::Instant::now(),
disconnect: None,
pending_keepalives: 0,
self_ref: Some(SelfRef {
inner: weak.clone(),
lock_waiters: waiters,
reader: reader_ref,
}),
})
});
Self {
inner,
lock_waiters,
reader,
}
}
}
struct LockTicket<'a> {
counter: &'a core::sync::atomic::AtomicUsize,
}
impl<'a> LockTicket<'a> {
fn new(counter: &'a core::sync::atomic::AtomicUsize) -> Self {
counter.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
Self { counter }
}
}
impl Drop for LockTicket<'_> {
fn drop(&mut self) {
self.counter
.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
}
}
impl SharedClient {
fn pump_step<'a>(
&'a self,
g: std::sync::MutexGuard<'a, Inner>,
) -> std::io::Result<(std::sync::MutexGuard<'a, Inner>, std::io::Result<()>)> {
let mut g = g;
let Some(reader) = &self.reader else {
let res = OwnedChannelStream::pump_one_step(&mut g, false);
return Ok((g, res));
};
if !g.client.has_buffered_input() {
drop(g);
let filled = reader.fill(PUMP_POLL);
g = lock_or_poison_io(&self.inner)?;
if let Err(e) = filled {
return Ok((g, Err(e)));
}
}
let res = OwnedChannelStream::pump_one_step(&mut g, true);
Ok((g, res))
}
pub fn sftp(&self) -> Result<SftpSession> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let id = open_session_under_lock(&mut g, "sftp")?;
send_request_and_await(
&mut g,
id,
ChannelRequest::Subsystem {
name: "sftp".into(),
},
"sftp: subsystem",
)?;
id
};
let stream = OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
};
match SftpClient::new(stream) {
Ok(c) => Ok(SftpSession {
_shared: self.clone(),
inner: c,
}),
Err(e) => {
Err(Error::Protocol(match e {
SftpError::Protocol(s) => s,
_ => "sftp: handshake failed",
}))
}
}
}
pub fn exec_stream(&self, command: &str) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let id = open_session_under_lock(&mut g, "exec")?;
send_request_and_await(
&mut g,
id,
ChannelRequest::Exec {
command: command.into(),
},
"exec: command",
)?;
id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn shell(&self, term: &str, cols: u32, rows: u32) -> Result<OwnedChannelStream> {
self.shell_stream(term, cols, rows, 0, 0, Vec::new())
}
pub fn shell_stream(
&self,
term: &str,
cols: u32,
rows: u32,
px_w: u32,
px_h: u32,
modes: Vec<u8>,
) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let id = open_session_under_lock(&mut g, "shell")?;
send_request_and_await(
&mut g,
id,
ChannelRequest::PtyReq {
term: term.into(),
cols,
rows,
px_w,
px_h,
modes,
},
"shell: pty-req",
)?;
send_request_and_await(&mut g, id, ChannelRequest::Shell, "shell: shell-req")?;
id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn shell_stream_no_pty(&self) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let id = open_session_under_lock(&mut g, "shell")?;
send_request_and_await(&mut g, id, ChannelRequest::Shell, "shell: shell-req")?;
id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn session_stream(
&self,
what: SessionRequest<'_>,
pty: Option<PtyRequest>,
) -> Result<OwnedChannelStream> {
let mut pty_denied = false;
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let id = open_session_under_lock(&mut g, "session")?;
if let Some(p) = pty {
match send_request_and_await(
&mut g,
id,
ChannelRequest::PtyReq {
term: p.term,
cols: p.cols,
rows: p.rows,
px_w: p.px_w,
px_h: p.px_h,
modes: p.modes,
},
"session: pty-req",
) {
Ok(()) => {}
Err(Error::Protocol("PTY allocation request failed")) => pty_denied = true,
Err(e) => return Err(e),
}
}
let (req, what_msg) = match what {
SessionRequest::Shell => (ChannelRequest::Shell, "session: shell"),
SessionRequest::Exec(command) => (
ChannelRequest::Exec {
command: command.into(),
},
"session: exec",
),
SessionRequest::Subsystem(name) => (
ChannelRequest::Subsystem { name: name.into() },
"session: subsystem",
),
};
send_request_and_await(&mut g, id, req, what_msg)?;
id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied,
})
}
pub fn pump_until_closed(&self) -> Result<()> {
self.pump_while(|| true)
}
pub fn pump_while(&self, keep_going: impl Fn() -> bool) -> Result<()> {
self.set_read_timeout(Some(Duration::from_millis(50)))?;
while keep_going() {
let pumped = {
let _ticket = LockTicket::new(&self.lock_waiters);
let mut g = lock_or_poison(&self.inner)?;
if g.pumping {
false
} else {
g.pumping = true;
let (mut g, res) = self.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
res.map_err(Error::from)?;
true
}
};
std::thread::sleep(if pumped {
Duration::from_micros(200)
} else {
Duration::from_millis(10)
});
}
Ok(())
}
pub fn set_channel_handlers(&self, handlers: crate::client::ClientHandlers) {
if let Ok(mut g) = self.inner.lock() {
g.handlers = Some(IncomingHandlers {
forwarded_tcpip: handlers.on_forwarded_tcpip,
forwarded_streamlocal: handlers.on_forwarded_streamlocal,
auth_agent: handlers.on_auth_agent,
x11: handlers.on_x11,
});
}
}
pub fn send_window_change(
&self,
channel: u32,
cols: u32,
rows: u32,
px_w: u32,
px_h: u32,
) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
let payload = g.client.conn.send_request(
channel,
ChannelRequest::WindowChange {
cols,
rows,
px_w,
px_h,
},
false,
)?;
g.client.write_payload(&payload)?;
Ok(())
}
pub fn channel_send_data(&self, channel: u32, data: &[u8]) -> std::io::Result<usize> {
if data.is_empty() {
return Ok(0);
}
let _ticket = LockTicket::new(&self.lock_waiters);
loop {
let mut g = lock_or_poison_io(&self.inner)?;
let (payload, taken) = g.client.conn.send_data(channel, data).map_err(io_err)?;
if taken > 0 {
g.client.write_payload(&payload).map_err(io_err)?;
return Ok(taken);
}
let queue = g.queues.entry(channel).or_default();
if queue.remote_close {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"channel closed by peer mid-write",
));
}
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
drop(g);
res?;
} else {
let cv = notifier_for(&mut g, channel);
let waited = cv.wait_timeout(g, WAIT_TIMEOUT).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})?;
drop(waited.0);
}
}
}
pub fn channel_recv_stderr(&self, channel: u32, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let _ticket = LockTicket::new(&self.lock_waiters);
loop {
let mut g = lock_or_poison_io(&self.inner)?;
let queue = g.queues.entry(channel).or_default();
if !queue.stderr.is_empty() {
let n = OwnedChannelStream::drain_into(queue, Stream::Stderr, buf);
replenish_under_lock(&mut g, channel, n as u32)?;
return Ok(n);
}
if queue.remote_eof {
return Ok(0);
}
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
drop(g);
res?;
if self.lock_waiters.load(core::sync::atomic::Ordering::SeqCst) > 1 {
std::thread::sleep(Duration::from_millis(1));
} else {
std::thread::sleep(Duration::from_micros(100));
}
} else {
let cv = notifier_for(&mut g, channel);
let waited = cv.wait_timeout(g, WAIT_TIMEOUT).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})?;
drop(waited.0);
}
}
}
pub fn channel_send_eof(&self, channel: u32) -> std::io::Result<()> {
let _ticket = LockTicket::new(&self.lock_waiters);
let mut g = lock_or_poison_io(&self.inner)?;
let q = g.queues.entry(channel).or_default();
if q.local_eof_sent {
return Ok(());
}
q.local_eof_sent = true;
let payload = g.client.conn.send_eof(channel).map_err(io_err)?;
g.client.write_payload(&payload).map_err(io_err)?;
Ok(())
}
pub fn set_read_timeout(&self, t: Option<core::time::Duration>) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
g.client.set_read_timeout(t).map_err(Error::Io)
}
pub fn send_keepalive(&self) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
let p = g
.client
.conn
.send_global_request(crate::channel::GlobalRequest::Keepalive, true);
g.client.write_payload(&p)?;
g.pending_keepalives += 1;
Ok(())
}
pub fn request_tcpip_forward(&self, bind_address: &str, bind_port: u16) -> Result<u16> {
let mut g = lock_or_poison(&self.inner)?;
refuse_if_mismatched(&g)?;
let p = g.client.conn.send_global_request(
crate::channel::GlobalRequest::TcpipForward {
bind_address: bind_address.to_string(),
bind_port: bind_port as u32,
},
true,
);
g.client.write_payload(&p)?;
let data = await_global_reply_under_lock(&mut g)?;
let port = if bind_port == 0 {
let p = crate::format::Reader::new(&data)
.read_u32()
.map_err(|_| Error::Protocol("tcpip-forward: server omitted assigned-port tail"))?;
u16::try_from(p)
.map_err(|_| Error::Protocol("tcpip-forward: server returned out-of-range port"))?
} else {
bind_port
};
g.client
.tcpip_forward_grants
.push((bind_address.to_string(), port));
Ok(port)
}
pub fn cancel_tcpip_forward(&self, bind_address: &str, bind_port: u16) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
let p = g.client.conn.send_global_request(
crate::channel::GlobalRequest::CancelTcpipForward {
bind_address: bind_address.to_string(),
bind_port: bind_port as u32,
},
true,
);
g.client.write_payload(&p)?;
await_global_reply_under_lock(&mut g)?;
let grants = &mut g.client.tcpip_forward_grants;
if let Some(i) = grants
.iter()
.position(|(a, p)| a == bind_address && *p == bind_port)
.or_else(|| grants.iter().position(|(_, p)| *p == bind_port))
{
grants.swap_remove(i);
}
Ok(())
}
pub fn request_streamlocal_forward(&self, socket_path: &str) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
refuse_if_mismatched(&g)?;
let p = g.client.conn.send_global_request(
crate::channel::GlobalRequest::StreamlocalForward {
socket_path: socket_path.to_string(),
},
true,
);
g.client.write_payload(&p)?;
await_global_reply_under_lock(&mut g)?;
g.client
.streamlocal_forward_grants
.push(socket_path.to_string());
Ok(())
}
pub fn rekey(&self) -> Result<()> {
lock_or_poison(&self.inner)?.client.rekey()
}
pub fn send_break(&self, channel: u32, ms: u32) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
let payload = g.client.conn.send_request(
channel,
ChannelRequest::Other {
name: "break".into(),
raw: ms.to_be_bytes().to_vec(),
},
false,
)?;
g.client.write_payload(&payload)
}
pub fn global_request(&self, name: &str, data: &[u8]) -> Result<Vec<u8>> {
let mut g = lock_or_poison(&self.inner)?;
let p = g.client.conn.send_global_request(
crate::channel::GlobalRequest::Other {
name: name.to_string(),
raw: data.to_vec(),
},
true,
);
g.client.write_payload(&p)?;
await_global_reply_under_lock(&mut g)
}
pub fn host_key_mismatched(&self) -> bool {
lock_or_poison(&self.inner).is_ok_and(|g| g.client.host_key_mismatched())
}
pub fn negotiated(&self) -> Option<crate::transport::Negotiated> {
lock_or_poison(&self.inner).ok()?.client.negotiated()
}
pub fn traffic(&self) -> Result<crate::transport::TrafficStats> {
Ok(lock_or_poison(&self.inner)?.client.traffic())
}
pub fn session_id(&self) -> Result<Vec<u8>> {
Ok(lock_or_poison(&self.inner)?.client.session_id().to_vec())
}
pub fn session_binding(&self) -> Result<Option<crate::client::SessionBinding>> {
Ok(lock_or_poison(&self.inner)?.client.session_binding())
}
pub fn cancel_streamlocal_forward(&self, socket_path: &str) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
let p = g.client.conn.send_global_request(
crate::channel::GlobalRequest::CancelStreamlocalForward {
socket_path: socket_path.to_string(),
},
true,
);
g.client.write_payload(&p)?;
await_global_reply_under_lock(&mut g)?;
g.client
.streamlocal_forward_grants
.retain(|p| p != socket_path);
Ok(())
}
pub fn disconnect_reason(&self) -> Option<(u32, alloc::string::String)> {
lock_or_poison(&self.inner).ok()?.disconnect.clone()
}
pub fn idle_for(&self) -> Result<Duration> {
Ok(lock_or_poison(&self.inner)?.last_rx.elapsed())
}
pub fn send_ping(&self, data: &[u8]) -> Result<()> {
let mut g = lock_or_poison(&self.inner)?;
g.client.send_transport_ping(data)
}
pub fn open_direct_tcpip(
&self,
dest_host: &str,
dest_port: u16,
orig_host: &str,
orig_port: u16,
) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
refuse_if_mismatched(&g)?;
open_direct_tcpip_under_lock(&mut g, dest_host, dest_port, orig_host, orig_port)?
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn open_stdio_forward(
&self,
dest_host: &str,
dest_port: u16,
) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
open_direct_tcpip_under_lock(&mut g, dest_host, dest_port, "127.0.0.1", 0)?
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn open_direct_streamlocal(&self, socket_path: &str) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
refuse_if_mismatched(&g)?;
let (local_id, open_payload) = g.client.conn.open(ChannelOpen::DirectStreamlocal {
socket_path: socket_path.to_string(),
})?;
g.client.write_payload(&open_payload)?;
await_open_under_lock(&mut g, local_id, "direct-streamlocal")?;
local_id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn open_tun(&self, mode: u32, unit: u32) -> Result<OwnedChannelStream> {
let local_id = {
let mut g = lock_or_poison(&self.inner)?;
let (local_id, open_payload) = g.client.conn.open(ChannelOpen::Tun { mode, unit })?;
g.client.write_payload(&open_payload)?;
await_open_under_lock(&mut g, local_id, "tun")?;
g.queues.entry(local_id).or_default().datagram = true;
local_id
};
Ok(OwnedChannelStream {
shared: self.clone(),
channel: local_id,
local_close_sent: false,
pty_denied: false,
})
}
pub fn send_datagram(&self, channel: u32, data: &[u8]) -> std::io::Result<bool> {
let _ticket = LockTicket::new(&self.lock_waiters);
let mut g = lock_or_poison_io(&self.inner)?;
match g.client.conn.send_datagram(channel, data).map_err(io_err)? {
Some(payload) => {
g.client.write_payload(&payload).map_err(io_err)?;
Ok(true)
}
None => Ok(false),
}
}
pub fn recv_datagram(&self, channel: u32) -> std::io::Result<Option<Vec<u8>>> {
let _ticket = LockTicket::new(&self.lock_waiters);
loop {
let mut g = lock_or_poison_io(&self.inner)?;
let queue = g.queues.entry(channel).or_default();
if let Some(p) = queue.packets.pop_front() {
let n = p.len() as u32;
replenish_under_lock(&mut g, channel, n)?;
return Ok(Some(p));
}
if queue.remote_eof || queue.remote_close {
return Ok(None);
}
self.pump_or_wait(g, channel)?;
}
}
fn pump_or_wait(
&self,
mut g: std::sync::MutexGuard<'_, Inner>,
channel: u32,
) -> std::io::Result<()> {
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
drop(g);
res?;
if self.lock_waiters.load(core::sync::atomic::Ordering::SeqCst) > 1 {
std::thread::sleep(Duration::from_millis(1));
} else {
std::thread::sleep(Duration::from_micros(100));
}
} else {
let cv = notifier_for(&mut g, channel);
let waited = cv.wait_timeout(g, WAIT_TIMEOUT).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})?;
drop(waited.0);
}
Ok(())
}
#[cfg_attr(not(feature = "ffi"), allow(dead_code))]
pub(crate) fn with_client<R>(&self, f: impl FnOnce(&mut Client) -> R) -> R {
let mut g = self.inner.lock().unwrap_or_else(|e| e.into_inner());
f(&mut g.client)
}
#[allow(dead_code)]
pub(crate) fn try_with_client<R>(&self, f: impl FnOnce(&mut Client) -> R) -> Result<R> {
let mut g = lock_or_poison(&self.inner)?;
Ok(f(&mut g.client))
}
pub fn with_session_env(&self, env: Vec<(String, String)>) {
if let Ok(mut g) = self.inner.lock() {
g.client.set_session_env(env);
}
}
}
fn open_session_under_lock(g: &mut Inner, what: &'static str) -> Result<u32> {
let (local_id, open_payload) = g.client.conn.open(ChannelOpen::Session)?;
g.client.write_payload(&open_payload)?;
let mut opened = false;
let mut iter_guard = 0usize;
while !opened {
iter_guard += 1;
if iter_guard > MAX_OPEN_ITER {
return Err(Error::Protocol(open_loop_msg(what)));
}
let payload = g.client.read_one_packet()?;
let ev = g.client.conn.on_packet(&payload)?;
match ev {
ChannelEvent::OpenConfirmed { channel } if channel == local_id => {
opened = true;
}
ChannelEvent::OpenFailed { channel, .. } if channel == local_id => {
return Err(Error::Protocol(open_failed_msg(what)));
}
other => dispatch_event(&mut *g, other),
}
}
g.client.maybe_send_auth_agent_req(local_id)?;
g.client.maybe_send_x11_req(local_id)?;
g.client.maybe_send_env(local_id)?;
g.queues.entry(local_id).or_default();
Ok(local_id)
}
fn refuse_if_mismatched(g: &Inner) -> Result<()> {
if g.client.host_key_mismatched() {
return Err(Error::Config(crate::client::HOST_KEY_MISMATCH_DISABLED));
}
Ok(())
}
fn open_direct_tcpip_under_lock(
g: &mut Inner,
dest_host: &str,
dest_port: u16,
orig_host: &str,
orig_port: u16,
) -> Result<u32> {
let (local_id, open_payload) = g.client.conn.open(ChannelOpen::DirectTcpip {
dest_host: dest_host.to_string(),
dest_port: dest_port as u32,
orig_host: orig_host.to_string(),
orig_port: orig_port as u32,
})?;
g.client.write_payload(&open_payload)?;
await_open_under_lock(g, local_id, "direct-tcpip")?;
Ok(local_id)
}
fn await_global_reply_under_lock(g: &mut Inner) -> Result<Vec<u8>> {
let mut iter_guard = 0usize;
loop {
iter_guard += 1;
if iter_guard > MAX_OPEN_ITER {
return Err(Error::Protocol(
"global request: reply loop did not converge",
));
}
let payload = g.client.read_one_packet()?;
g.last_rx = std::time::Instant::now();
match g.client.conn.on_packet(&payload)? {
ChannelEvent::GlobalSuccess { .. } | ChannelEvent::GlobalFailure
if g.pending_keepalives > 0 =>
{
g.pending_keepalives -= 1;
}
ChannelEvent::GlobalSuccess { data } => return Ok(data),
ChannelEvent::GlobalFailure => return Err(Error::Protocol("global request denied")),
other => dispatch_event(&mut *g, other),
}
}
}
fn await_open_under_lock(g: &mut Inner, local_id: u32, what: &'static str) -> Result<()> {
let mut iter_guard = 0usize;
loop {
iter_guard += 1;
if iter_guard > MAX_OPEN_ITER {
return Err(Error::Protocol(open_loop_msg(what)));
}
let payload = g.client.read_one_packet()?;
let ev = g.client.conn.on_packet(&payload)?;
match ev {
ChannelEvent::OpenConfirmed { channel } if channel == local_id => break,
ChannelEvent::OpenFailed { channel, .. } if channel == local_id => {
return Err(Error::Protocol(open_failed_msg(what)));
}
other => dispatch_event(&mut *g, other),
}
}
g.queues.entry(local_id).or_default();
Ok(())
}
fn send_request_and_await(
g: &mut Inner,
local_id: u32,
req: ChannelRequest,
what: &'static str,
) -> Result<()> {
let payload = g.client.conn.send_request(local_id, req, true)?;
g.client.write_payload(&payload)?;
let mut iter_guard = 0usize;
loop {
iter_guard += 1;
if iter_guard > MAX_OPEN_ITER {
return Err(Error::Protocol(reply_loop_msg(what)));
}
let payload = g.client.read_one_packet()?;
let ev = g.client.conn.on_packet(&payload)?;
match ev {
ChannelEvent::Success { channel } if channel == local_id => return Ok(()),
ChannelEvent::Failure { channel } if channel == local_id => {
return Err(Error::Protocol(reply_failed_msg(what)));
}
other => dispatch_event(&mut *g, other),
}
}
}
fn open_loop_msg(what: &'static str) -> &'static str {
match what {
"sftp" => "sftp: open loop did not converge",
"exec" => "exec: open loop did not converge",
"shell" => "shell: open loop did not converge",
"direct-tcpip" => "direct-tcpip: open loop did not converge",
"direct-streamlocal" => "direct-streamlocal: open loop did not converge",
"tun" => "tun: open loop did not converge",
_ => "channel: open loop did not converge",
}
}
fn open_failed_msg(what: &'static str) -> &'static str {
match what {
"sftp" => "sftp: channel open failed",
"exec" => "exec: channel open failed",
"shell" => "shell: channel open failed",
"direct-tcpip" => "direct-tcpip: open failed",
"direct-streamlocal" => "direct-streamlocal: open failed",
"tun" => "tun: open failed",
_ => "channel: open failed",
}
}
fn reply_loop_msg(what: &'static str) -> &'static str {
match what {
"sftp: subsystem" => "sftp: subsystem-reply loop did not converge",
"exec: command" => "exec: command-reply loop did not converge",
"shell: pty-req" => "shell: pty-req-reply loop did not converge",
"shell: shell-req" => "shell: shell-req-reply loop did not converge",
_ => "channel: request-reply loop did not converge",
}
}
fn reply_failed_msg(what: &'static str) -> &'static str {
match what {
"sftp: subsystem" => "sftp: subsystem request denied",
"exec: command" => "exec: command request denied",
"shell: pty-req" => "shell: pty-req denied",
"shell: shell-req" => "shell: shell-req denied",
"session: pty-req" => "PTY allocation request failed",
"session: shell" => "shell request failed",
"session: exec" => "exec request failed",
"session: subsystem" => "subsystem request failed",
_ => "channel: request denied",
}
}
fn notifier_for(g: &mut Inner, channel: u32) -> Arc<Condvar> {
g.notifiers
.entry(channel)
.or_insert_with(|| Arc::new(Condvar::new()))
.clone()
}
enum IncomingKind {
ForwardedTcpip(
Arc<crate::client::ForwardedTcpipCallback>,
crate::client::ForwardedTcpipOrigin,
),
ForwardedStreamlocal(
Arc<crate::client::ForwardedStreamlocalCallback>,
crate::client::ForwardedStreamlocalOrigin,
),
AuthAgent(Arc<crate::client::AuthAgentCallback>),
X11(Arc<crate::client::X11Callback>),
}
fn incoming_handler(g: &Inner, kind: &ChannelOpen) -> Option<IncomingKind> {
let h = g.handlers.as_ref()?;
match kind {
ChannelOpen::ForwardedTcpip {
dest_host,
dest_port,
orig_host,
orig_port,
} if !g.client.tcpip_forward_grants.is_empty() => {
let (Ok(bound_port), Ok(orig_port)) =
(u16::try_from(*dest_port), u16::try_from(*orig_port))
else {
return None;
};
Some(IncomingKind::ForwardedTcpip(
h.forwarded_tcpip.clone()?,
crate::client::ForwardedTcpipOrigin {
bound_address: dest_host.clone(),
bound_port,
orig_address: orig_host.clone(),
orig_port,
},
))
}
ChannelOpen::ForwardedStreamlocal { socket_path }
if !g.client.streamlocal_forward_grants.is_empty() =>
{
Some(IncomingKind::ForwardedStreamlocal(
h.forwarded_streamlocal.clone()?,
crate::client::ForwardedStreamlocalOrigin {
socket_path: socket_path.clone(),
},
))
}
ChannelOpen::AuthAgent
if g.client.request_auth_agent && !g.client.host_key_mismatched() =>
{
Some(IncomingKind::AuthAgent(h.auth_agent.clone()?))
}
ChannelOpen::X11 { .. } if g.client.wants_x11() && !g.client.host_key_mismatched() => {
Some(IncomingKind::X11(h.x11.clone()?))
}
_ => None,
}
}
fn accept_incoming(g: &mut Inner, channel: u32, kind: IncomingKind) {
let Some(self_ref) = g.self_ref.as_ref() else {
return;
};
let Some(arc) = self_ref.inner.upgrade() else {
return;
};
let shared = SharedClient {
inner: arc,
lock_waiters: self_ref.lock_waiters.clone(),
reader: self_ref.reader.clone(),
};
match g.client.conn.accept_open(channel) {
Ok(p) => {
if g.client.write_payload(&p).is_err() {
return;
}
}
Err(_) => return,
}
g.queues.entry(channel).or_default();
let mut stream = OwnedChannelStream {
shared: shared.clone(),
channel,
local_close_sent: false,
pty_denied: false,
};
let (ingress_tx, ingress_rx) =
std::sync::mpsc::sync_channel::<Option<Vec<u8>>>(INCOMING_INGRESS_BACKLOG);
let (egress_tx, egress_rx) =
std::sync::mpsc::sync_channel::<crate::stream::ChannelEgress>(INCOMING_EGRESS_BACKLOG);
let cs = crate::stream::ChannelStream::new(ingress_rx, egress_tx);
std::thread::spawn(move || match kind {
IncomingKind::ForwardedTcpip(cb, origin) => cb(origin, cs),
IncomingKind::ForwardedStreamlocal(cb, origin) => cb(origin, cs),
IncomingKind::AuthAgent(cb) => cb(cs),
IncomingKind::X11(cb) => cb(cs),
});
let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
let out = shared;
std::thread::spawn(move || {
use crate::stream::ChannelEgress;
let _done = done_tx;
while let Ok(msg) = egress_rx.recv() {
match msg {
ChannelEgress::Data(bytes) => {
let mut off = 0;
while off < bytes.len() {
match out.channel_send_data(channel, &bytes[off..]) {
Ok(0) | Err(_) => return,
Ok(n) => off += n,
}
}
}
ChannelEgress::Eof => {
let _ = out.channel_send_eof(channel);
}
ChannelEgress::Close => break,
_ => {}
}
}
let _ = out.channel_send_eof(channel);
});
std::thread::spawn(move || {
let mut buf = vec![0u8; 32 * 1024];
loop {
match stream.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => {
if ingress_tx.send(Some(buf[..n].to_vec())).is_err() {
break;
}
}
}
}
let _ = ingress_tx.send(None);
let _ = done_rx.recv();
drop(ingress_tx);
drop(stream);
});
}
const INCOMING_EGRESS_BACKLOG: usize = 64;
const INCOMING_INGRESS_BACKLOG: usize = 32;
fn dispatch_event(g: &mut Inner, ev: ChannelEvent) {
if let ChannelEvent::OpenRequest { channel, kind } = &ev
&& let Some(handler) = incoming_handler(g, kind)
{
accept_incoming(g, *channel, handler);
return;
}
if matches!(
ev,
ChannelEvent::GlobalSuccess { .. } | ChannelEvent::GlobalFailure
) {
g.pending_keepalives = g.pending_keepalives.saturating_sub(1);
}
g.client.notify_global_request(&ev);
if let Ok(Some(p)) = crate::client::unsolicited_reply(&mut g.client.conn, &ev) {
let _ = g.client.write_payload(&p);
return;
}
let target = match &ev {
ChannelEvent::Data { channel, .. }
| ChannelEvent::ExtendedData { channel, .. }
| ChannelEvent::Eof { channel }
| ChannelEvent::Close { channel } => Some(*channel),
_ => None,
};
stash_event(&mut g.queues, ev);
if let Some(ch) = target
&& let Some(cv) = g.notifiers.get(&ch)
{
cv.notify_all();
}
}
fn stash_event(queues: &mut BTreeMap<u32, ChannelQueue>, ev: ChannelEvent) {
match ev {
ChannelEvent::Data { channel, data } => {
if let Some(q) = queues.get_mut(&channel) {
if q.datagram {
q.packets.push_back(data);
} else {
q.data.extend(data);
}
}
}
ChannelEvent::ExtendedData { channel, data, .. } => {
if let Some(q) = queues.get_mut(&channel) {
q.stderr.extend(data);
}
}
ChannelEvent::Eof { channel } => {
if let Some(q) = queues.get_mut(&channel) {
q.remote_eof = true;
}
}
ChannelEvent::Close { channel } => {
if let Some(q) = queues.get_mut(&channel) {
q.remote_eof = true;
q.remote_close = true;
}
}
ChannelEvent::Request {
channel, request, ..
} => {
let Some(q) = queues.get_mut(&channel) else {
return;
};
match request {
ChannelRequest::ExitStatus { code } => q.exit_status = Some(code as i32),
ChannelRequest::ExitSignal { name, .. } => q.exit_signal = Some(name),
_ => {}
}
}
_ => {}
}
}
fn replenish_under_lock(g: &mut Inner, channel: u32, n: u32) -> std::io::Result<()> {
if n == 0 {
return Ok(());
}
if let Some(adj) = g.client.conn.replenish_window(channel, n).map_err(io_err)? {
g.client.write_payload(&adj).map_err(io_err)?;
}
Ok(())
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum SessionRequest<'a> {
Shell,
Exec(&'a str),
Subsystem(&'a str),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PtyRequest {
pub term: String,
pub cols: u32,
pub rows: u32,
pub px_w: u32,
pub px_h: u32,
pub modes: Vec<u8>,
}
impl PtyRequest {
pub fn new(term: impl Into<String>, cols: u32, rows: u32) -> Self {
Self {
term: term.into(),
cols,
rows,
px_w: 0,
px_h: 0,
modes: Vec::new(),
}
}
}
pub struct OwnedChannelStream {
shared: SharedClient,
channel: u32,
local_close_sent: bool,
pty_denied: bool,
}
#[derive(Clone, Copy)]
enum Stream {
Data,
Stderr,
}
impl OwnedChannelStream {
pub fn pty_denied(&self) -> bool {
self.pty_denied
}
pub fn channel_id(&self) -> u32 {
self.channel
}
pub fn send_eof(&mut self) -> std::io::Result<()> {
let mut g = lock_or_poison_io(&self.shared.inner)?;
let q = g.queues.entry(self.channel).or_default();
if q.local_eof_sent {
return Ok(());
}
q.local_eof_sent = true;
let payload = g.client.conn.send_eof(self.channel).map_err(io_err)?;
g.client.write_payload(&payload).map_err(io_err)?;
Ok(())
}
pub fn exit_status(&self) -> Option<i32> {
let g = self.shared.inner.lock().ok()?;
g.queues.get(&self.channel).and_then(|q| q.exit_status)
}
pub fn wait_exit_status(&mut self, timeout: Option<Duration>) -> Option<i32> {
let deadline = timeout.map(|t| std::time::Instant::now() + t);
let _ticket = LockTicket::new(&self.shared.lock_waiters);
loop {
let mut g = self.shared.inner.lock().ok()?;
let q = g.queues.entry(self.channel).or_default();
if q.exit_status.is_some() || q.exit_signal.is_some() || q.remote_close {
return q.exit_status;
}
if deadline.is_some_and(|d| std::time::Instant::now() >= d) {
return None;
}
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.shared.pump_step(g).ok()?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
if res.is_err() {
return g.queues.get(&self.channel).and_then(|q| q.exit_status);
}
} else {
let cv = notifier_for(&mut g, self.channel);
let _ = cv.wait_timeout(g, WAIT_TIMEOUT);
}
}
}
pub fn exit_signal(&self) -> Option<String> {
let g = self.shared.inner.lock().ok()?;
g.queues
.get(&self.channel)
.and_then(|q| q.exit_signal.clone())
}
fn drain_into(queue: &mut ChannelQueue, stream: Stream, buf: &mut [u8]) -> usize {
let src = match stream {
Stream::Data => &mut queue.data,
Stream::Stderr => &mut queue.stderr,
};
let mut n = 0;
for slot in buf.iter_mut() {
match src.pop_front() {
Some(b) => {
*slot = b;
n += 1;
}
None => break,
}
}
n
}
fn read_stream(&mut self, stream: Stream, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let _ticket = LockTicket::new(&self.shared.lock_waiters);
loop {
let mut g = lock_or_poison_io(&self.shared.inner)?;
let queue = g.queues.entry(self.channel).or_default();
let avail = match stream {
Stream::Data => !queue.data.is_empty(),
Stream::Stderr => !queue.stderr.is_empty(),
};
if avail {
let n = Self::drain_into(queue, stream, buf);
replenish_under_lock(&mut g, self.channel, n as u32)?;
return Ok(n);
}
if queue.remote_eof {
return Ok(0);
}
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.shared.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
drop(g);
res?;
if self
.shared
.lock_waiters
.load(core::sync::atomic::Ordering::SeqCst)
> 1
{
std::thread::sleep(Duration::from_millis(1));
} else {
std::thread::sleep(Duration::from_micros(100));
}
} else {
let cv = notifier_for(&mut g, self.channel);
let waited = cv.wait_timeout(g, WAIT_TIMEOUT).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})?;
drop(waited.0);
}
}
}
pub fn read_stderr(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read_stream(Stream::Stderr, buf)
}
fn pump_one_step(g: &mut Inner, stashed: bool) -> std::io::Result<()> {
let read = if stashed {
g.client.read_stashed_packet()
} else {
g.client.read_one_packet_maybe_timeout()
};
if let Err(Error::Disconnected {
reason,
description,
}) = &read
{
g.disconnect = Some((*reason, description.clone()));
}
let Some(payload) = read.map_err(io_err)? else {
return Ok(());
};
g.last_rx = std::time::Instant::now();
let ev = g.client.conn.on_packet(&payload).map_err(io_err)?;
dispatch_event(g, ev);
Ok(())
}
}
impl Read for OwnedChannelStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read_stream(Stream::Data, buf)
}
}
impl Write for OwnedChannelStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let _ticket = LockTicket::new(&self.shared.lock_waiters);
loop {
let mut g = lock_or_poison_io(&self.shared.inner)?;
let (payload, taken) = g.client.conn.send_data(self.channel, buf).map_err(io_err)?;
if taken > 0 {
g.client.write_payload(&payload).map_err(io_err)?;
return Ok(taken);
}
let queue = g.queues.entry(self.channel).or_default();
if queue.remote_close {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"channel closed by peer mid-write",
));
}
if !g.pumping {
g.pumping = true;
let (mut g, res) = self.shared.pump_step(g)?;
g.pumping = false;
for cv in g.notifiers.values() {
cv.notify_one();
}
drop(g);
res?;
} else {
let cv = notifier_for(&mut g, self.channel);
let waited = cv.wait_timeout(g, WAIT_TIMEOUT).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"SharedClient mutex poisoned",
)
})?;
drop(waited.0);
}
}
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Transport for OwnedChannelStream {
fn set_read_timeout(&mut self, t: Option<Duration>) -> std::io::Result<()> {
self.shared.set_read_timeout(t).map_err(io_err)
}
}
impl Drop for OwnedChannelStream {
fn drop(&mut self) {
let Ok(mut g) = self.shared.inner.lock() else {
return; };
if !self.local_close_sent {
if let Ok(p) = g.client.conn.send_eof(self.channel) {
let _ = g.client.write_payload(&p);
}
if let Ok(p) = g.client.conn.send_close(self.channel) {
let _ = g.client.write_payload(&p);
}
self.local_close_sent = true;
}
const MAX_DRAIN: usize = 128;
for _ in 0..MAX_DRAIN {
let already_closed = g
.queues
.get(&self.channel)
.map(|q| q.remote_close)
.unwrap_or(false);
if already_closed {
break;
}
if Self::pump_one_step(&mut g, false).is_err() {
break;
}
}
g.queues.remove(&self.channel);
g.notifiers.remove(&self.channel);
}
}
pub struct SftpSession {
_shared: SharedClient,
inner: SftpClient<OwnedChannelStream>,
}
impl SftpSession {
pub fn server_version(&self) -> u32 {
self.inner.server_version()
}
pub fn extensions(&self) -> &[(Vec<u8>, Vec<u8>)] {
self.inner.extensions()
}
pub fn open(
&mut self,
path: &[u8],
pflags: u32,
attrs: crate::sftp::Attrs,
) -> SftpResult<Vec<u8>> {
self.inner.open(path, pflags, attrs)
}
pub fn close(&mut self, handle: &[u8]) -> SftpResult<()> {
self.inner.close(handle)
}
pub fn read(&mut self, handle: &[u8], offset: u64, len: u32) -> SftpResult<Vec<u8>> {
self.inner.read(handle, offset, len)
}
pub fn write(&mut self, handle: &[u8], offset: u64, data: &[u8]) -> SftpResult<()> {
self.inner.write(handle, offset, data)
}
pub fn stat(&mut self, path: &[u8]) -> SftpResult<crate::sftp::Attrs> {
self.inner.stat(path)
}
pub fn lstat(&mut self, path: &[u8]) -> SftpResult<crate::sftp::Attrs> {
self.inner.lstat(path)
}
pub fn fstat(&mut self, handle: &[u8]) -> SftpResult<crate::sftp::Attrs> {
self.inner.fstat(handle)
}
pub fn setstat(&mut self, path: &[u8], attrs: crate::sftp::Attrs) -> SftpResult<()> {
self.inner.setstat(path, attrs)
}
pub fn fsetstat(&mut self, handle: &[u8], attrs: crate::sftp::Attrs) -> SftpResult<()> {
self.inner.fsetstat(handle, attrs)
}
pub fn opendir(&mut self, path: &[u8]) -> SftpResult<Vec<u8>> {
self.inner.opendir(path)
}
pub fn readdir(&mut self, handle: &[u8]) -> SftpResult<Option<Vec<crate::sftp::NameEntry>>> {
self.inner.readdir(handle)
}
pub fn mkdir(&mut self, path: &[u8], attrs: crate::sftp::Attrs) -> SftpResult<()> {
self.inner.mkdir(path, attrs)
}
pub fn rmdir(&mut self, path: &[u8]) -> SftpResult<()> {
self.inner.rmdir(path)
}
pub fn remove(&mut self, path: &[u8]) -> SftpResult<()> {
self.inner.remove(path)
}
pub fn rename(&mut self, oldpath: &[u8], newpath: &[u8]) -> SftpResult<()> {
self.inner.rename(oldpath, newpath)
}
pub fn symlink(&mut self, target_path: &[u8], link_path: &[u8]) -> SftpResult<()> {
self.inner.symlink(target_path, link_path)
}
pub fn readlink(&mut self, path: &[u8]) -> SftpResult<Vec<u8>> {
self.inner.readlink(path)
}
pub fn realpath(&mut self, path: &[u8]) -> SftpResult<Vec<u8>> {
self.inner.realpath(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channel_queue_default_is_empty() {
let q = ChannelQueue::default();
assert!(q.data.is_empty());
assert!(q.stderr.is_empty());
assert!(!q.remote_eof);
assert!(!q.remote_close);
}
#[test]
fn drain_into_partial() {
let mut q = ChannelQueue::default();
q.data.extend(b"hello".iter().copied());
let mut buf = [0u8; 3];
let n = OwnedChannelStream::drain_into(&mut q, Stream::Data, &mut buf);
assert_eq!(n, 3);
assert_eq!(&buf, b"hel");
assert_eq!(q.data.iter().copied().collect::<Vec<_>>(), b"lo");
}
#[test]
fn drain_into_overflow() {
let mut q = ChannelQueue::default();
q.data.extend(b"hi".iter().copied());
let mut buf = [0u8; 8];
let n = OwnedChannelStream::drain_into(&mut q, Stream::Data, &mut buf);
assert_eq!(n, 2);
assert_eq!(&buf[..2], b"hi");
assert!(q.data.is_empty());
}
#[test]
fn drain_into_stderr() {
let mut q = ChannelQueue::default();
q.stderr.extend(b"err".iter().copied());
q.data.extend(b"std".iter().copied());
let mut buf = [0u8; 8];
let n = OwnedChannelStream::drain_into(&mut q, Stream::Stderr, &mut buf);
assert_eq!(n, 3);
assert_eq!(&buf[..3], b"err");
assert!(q.stderr.is_empty());
assert_eq!(q.data.iter().copied().collect::<Vec<_>>(), b"std");
}
#[test]
fn stash_event_drops_events_for_unregistered_channels() {
let mut queues: BTreeMap<u32, ChannelQueue> = BTreeMap::new();
queues.insert(1, ChannelQueue::default());
for ev in [
ChannelEvent::Data {
channel: 5,
data: b"late".to_vec(),
},
ChannelEvent::ExtendedData {
channel: 5,
code: 1,
data: b"late".to_vec(),
},
ChannelEvent::Eof { channel: 5 },
ChannelEvent::Close { channel: 5 },
ChannelEvent::Request {
channel: 5,
request: ChannelRequest::ExitStatus { code: 1 },
want_reply: false,
},
] {
stash_event(&mut queues, ev);
}
assert_eq!(queues.len(), 1, "no mailbox may be created for channel 5");
assert!(!queues.contains_key(&5));
assert!(queues[&1].data.is_empty());
assert!(!queues[&1].remote_close);
}
#[test]
fn stash_event_data_appends_to_right_channel() {
let mut queues: BTreeMap<u32, ChannelQueue> = BTreeMap::new();
queues.insert(7, ChannelQueue::default());
queues.insert(9, ChannelQueue::default());
stash_event(
&mut queues,
ChannelEvent::Data {
channel: 7,
data: b"abc".to_vec(),
},
);
stash_event(
&mut queues,
ChannelEvent::Data {
channel: 7,
data: b"def".to_vec(),
},
);
stash_event(
&mut queues,
ChannelEvent::Data {
channel: 9,
data: b"x".to_vec(),
},
);
assert_eq!(
queues[&7].data.iter().copied().collect::<Vec<_>>(),
b"abcdef"
);
assert_eq!(queues[&9].data.iter().copied().collect::<Vec<_>>(), b"x");
}
#[test]
fn stash_event_eof_and_close_set_flags() {
let mut queues: BTreeMap<u32, ChannelQueue> = BTreeMap::new();
queues.insert(3, ChannelQueue::default());
stash_event(&mut queues, ChannelEvent::Eof { channel: 3 });
assert!(queues[&3].remote_eof);
assert!(!queues[&3].remote_close);
stash_event(&mut queues, ChannelEvent::Close { channel: 3 });
assert!(queues[&3].remote_eof);
assert!(queues[&3].remote_close);
}
#[test]
fn notifier_map_round_trips_arc_identity() {
let mut notifiers: BTreeMap<u32, Arc<Condvar>> = BTreeMap::new();
let cv1 = notifiers
.entry(7)
.or_insert_with(|| Arc::new(Condvar::new()))
.clone();
let cv2 = notifiers
.entry(7)
.or_insert_with(|| Arc::new(Condvar::new()))
.clone();
assert!(Arc::ptr_eq(&cv1, &cv2));
let cv3 = notifiers
.entry(9)
.or_insert_with(|| Arc::new(Condvar::new()))
.clone();
assert!(!Arc::ptr_eq(&cv1, &cv3));
}
#[test]
fn stash_event_ignores_irrelevant() {
let mut queues: BTreeMap<u32, ChannelQueue> = BTreeMap::new();
stash_event(&mut queues, ChannelEvent::OpenConfirmed { channel: 1 });
stash_event(
&mut queues,
ChannelEvent::OpenFailed {
channel: 1,
reason: 0,
description: String::new(),
},
);
stash_event(
&mut queues,
ChannelEvent::WindowAdjust {
channel: 1,
added: 100,
},
);
assert!(queues.is_empty());
}
}