use std::time::{Duration, Instant};
use crate::error::{Error, Result};
pub mod docker;
pub mod local;
pub mod ssh;
pub trait Transport: Send {
fn write_all(&mut self, bytes: &[u8]) -> Result<()>;
fn recv_timeout(&self, dur: Duration) -> Option<Vec<u8>>;
}
pub(crate) fn shell_init(t: &mut dyn Transport) -> Result<()> {
const READY: &[u8] = b"EXECKITrdy9f3a7cok";
t.write_all(
b"stty -echo 2>/dev/null; PS1=''; PS2=''; PROMPT_COMMAND=''; \
command set +o history 2>/dev/null; command set +H 2>/dev/null; command set +m 2>/dev/null; \
unset HISTFILE; \
command export PAGER=cat GIT_PAGER=cat MANPAGER=cat SYSTEMD_PAGER=cat LESS=FRX 2>/dev/null; \
__ek_dp=$(command -v base64 2>/dev/null); \
if printf 'YQ==' | \"$__ek_dp\" -d >/dev/null 2>&1; then __ek_df=-d; \
elif printf 'YQ==' | \"$__ek_dp\" -D >/dev/null 2>&1; then __ek_df=-D; \
else __ek_dp=''; fi; \
printf '%s\\n' EXECKITrdy''9f3a7c\"${__ek_dp:+ok}\"''\n",
)?;
let mut acc = Vec::new();
let deadline = Instant::now() + Duration::from_secs(8);
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Err(Error::Transport("shell init timed out".into()));
}
match t.recv_timeout(remaining) {
Some(c) => {
acc.extend_from_slice(&c);
if contains(&acc, READY) {
return Ok(());
}
if contains(&acc, b"EXECKITrdy9f3a7c\n") || contains(&acc, b"EXECKITrdy9f3a7c\r") {
return Err(Error::Transport(
"execkit needs 'base64' on the target shell (coreutils or busybox)".into(),
));
}
}
None => return Err(Error::Transport("shell init: disconnected".into())),
}
}
}
pub(crate) fn contains(hay: &[u8], needle: &[u8]) -> bool {
!needle.is_empty()
&& hay.len() >= needle.len()
&& hay.windows(needle.len()).any(|w| w == needle)
}