Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c40f5459ba | ||
|
|
601510687e | ||
|
|
60387e9222 |
Generated
+1
-1
@@ -436,7 +436,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dosh"
|
name = "dosh"
|
||||||
version = "0.1.8"
|
version = "0.1.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dosh"
|
name = "dosh"
|
||||||
version = "0.1.8"
|
version = "0.1.10"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
+19
-5
@@ -141,6 +141,20 @@ function Verify-ArchiveVersion($ExtractDir, $Url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Install-Binary($Source, $Destination) {
|
||||||
|
$dir = Split-Path -Parent $Destination
|
||||||
|
$name = Split-Path -Leaf $Destination
|
||||||
|
$tmp = Join-Path $dir ".$name.tmp.$PID"
|
||||||
|
Copy-Item $Source $tmp -Force
|
||||||
|
try {
|
||||||
|
Move-Item $tmp $Destination -Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$bindir = Join-Path $Prefix "bin"
|
$bindir = Join-Path $Prefix "bin"
|
||||||
$configDir = Join-Path $HOME ".config\dosh"
|
$configDir = Join-Path $HOME ".config\dosh"
|
||||||
New-Item -ItemType Directory -Force -Path $bindir, $configDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $bindir, $configDir | Out-Null
|
||||||
@@ -168,9 +182,9 @@ function Install-Prebuilt {
|
|||||||
if (-not $found) {
|
if (-not $found) {
|
||||||
throw "prebuilt archive missing $bin"
|
throw "prebuilt archive missing $bin"
|
||||||
}
|
}
|
||||||
Copy-Item $found.FullName (Join-Path $bindir $bin) -Force
|
Install-Binary $found.FullName (Join-Path $bindir $bin)
|
||||||
}
|
}
|
||||||
Copy-Item (Join-Path $bindir "dosh-client.exe") (Join-Path $bindir "dosh.exe") -Force
|
Install-Binary (Join-Path $bindir "dosh-client.exe") (Join-Path $bindir "dosh.exe")
|
||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -202,9 +216,9 @@ function Install-FromSource {
|
|||||||
try {
|
try {
|
||||||
Push-Location $src
|
Push-Location $src
|
||||||
cargo build --release --bin dosh-client --bin dosh-bench
|
cargo build --release --bin dosh-client --bin dosh-bench
|
||||||
Copy-Item "target\release\dosh-client.exe" (Join-Path $bindir "dosh-client.exe") -Force
|
Install-Binary "target\release\dosh-client.exe" (Join-Path $bindir "dosh-client.exe")
|
||||||
Copy-Item "target\release\dosh-client.exe" (Join-Path $bindir "dosh.exe") -Force
|
Install-Binary "target\release\dosh-client.exe" (Join-Path $bindir "dosh.exe")
|
||||||
Copy-Item "target\release\dosh-bench.exe" (Join-Path $bindir "dosh-bench.exe") -Force
|
Install-Binary "target\release\dosh-bench.exe" (Join-Path $bindir "dosh-bench.exe")
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Pop-Location
|
Pop-Location
|
||||||
|
|||||||
+19
-6
@@ -226,13 +226,26 @@ find_extracted_binary() {
|
|||||||
find "$1" -type f -name "$2" 2>/dev/null | sed -n '1p'
|
find "$1" -type f -name "$2" 2>/dev/null | sed -n '1p'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_binary() {
|
||||||
|
src="$1"
|
||||||
|
dst="$2"
|
||||||
|
dst_dir="$(dirname "$dst")"
|
||||||
|
dst_base="$(basename "$dst")"
|
||||||
|
tmp="$dst_dir/.$dst_base.tmp.$$"
|
||||||
|
install -m 0755 "$src" "$tmp"
|
||||||
|
if ! mv -f "$tmp" "$dst"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_extracted_binary() {
|
install_extracted_binary() {
|
||||||
found="$(find_extracted_binary "$1" "$2")"
|
found="$(find_extracted_binary "$1" "$2")"
|
||||||
if [ -z "$found" ]; then
|
if [ -z "$found" ]; then
|
||||||
echo "prebuilt archive missing $2" >&2
|
echo "prebuilt archive missing $2" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
install -m 0755 "$found" "$3"
|
install_binary "$found" "$3"
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256_file() {
|
sha256_file() {
|
||||||
@@ -329,7 +342,7 @@ try_install_prebuilt() {
|
|||||||
install_extracted_binary "$tmpdir/extract" dosh-auth "$bindir/dosh-auth" || return 1
|
install_extracted_binary "$tmpdir/extract" dosh-auth "$bindir/dosh-auth" || return 1
|
||||||
fi
|
fi
|
||||||
if found_bench="$(find_extracted_binary "$tmpdir/extract" dosh-bench)" && [ -n "$found_bench" ]; then
|
if found_bench="$(find_extracted_binary "$tmpdir/extract" dosh-bench)" && [ -n "$found_bench" ]; then
|
||||||
install -m 0755 "$found_bench" "$bindir/dosh-bench"
|
install_binary "$found_bench" "$bindir/dosh-bench"
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -388,14 +401,14 @@ install_from_source() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -m 0755 target/release/dosh-client "$bindir/dosh-client"
|
install_binary target/release/dosh-client "$bindir/dosh-client"
|
||||||
ln -sf dosh-client "$bindir/dosh"
|
ln -sf dosh-client "$bindir/dosh"
|
||||||
if [ "$role" = "server" ] || [ "$role" = "both" ]; then
|
if [ "$role" = "server" ] || [ "$role" = "both" ]; then
|
||||||
install -m 0755 target/release/dosh-server "$bindir/dosh-server"
|
install_binary target/release/dosh-server "$bindir/dosh-server"
|
||||||
install -m 0755 target/release/dosh-auth "$bindir/dosh-auth"
|
install_binary target/release/dosh-auth "$bindir/dosh-auth"
|
||||||
fi
|
fi
|
||||||
if [ -f target/release/dosh-bench ]; then
|
if [ -f target/release/dosh-bench ]; then
|
||||||
install -m 0755 target/release/dosh-bench "$bindir/dosh-bench"
|
install_binary target/release/dosh-bench "$bindir/dosh-bench"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ for test_name in \
|
|||||||
tui_control_sequences_survive_transport_verbatim \
|
tui_control_sequences_survive_transport_verbatim \
|
||||||
unicode_output_survives_transport \
|
unicode_output_survives_transport \
|
||||||
tui_graph_glyphs_survive_fast_repaints_without_snapshot_substitution \
|
tui_graph_glyphs_survive_fast_repaints_without_snapshot_substitution \
|
||||||
|
btop_style_graph_output_stays_raw_when_retransmit_history_overflows \
|
||||||
large_tui_paint_is_delivered_in_mtu_safe_frames \
|
large_tui_paint_is_delivered_in_mtu_safe_frames \
|
||||||
resume_snapshot_preserves_alternate_screen_mode
|
resume_snapshot_preserves_alternate_screen_mode
|
||||||
do
|
do
|
||||||
|
|||||||
+10
-15
@@ -3336,7 +3336,7 @@ async fn broadcast_output(
|
|||||||
let sends = {
|
let sends = {
|
||||||
let mut locked = state.lock().expect("server state poisoned");
|
let mut locked = state.lock().expect("server state poisoned");
|
||||||
let scrollback = locked.config.scrollback;
|
let scrollback = locked.config.scrollback;
|
||||||
let retransmit_window = locked.config.retransmit_window;
|
let retransmit_window = locked.config.retransmit_window.max(1);
|
||||||
let session = locked
|
let session = locked
|
||||||
.sessions
|
.sessions
|
||||||
.get_mut(&output.session)
|
.get_mut(&output.session)
|
||||||
@@ -3374,20 +3374,18 @@ async fn broadcast_output(
|
|||||||
client.send_seq += 1;
|
client.send_seq += 1;
|
||||||
// Count traffic toward the packet-count rekey trigger (spec §11).
|
// Count traffic toward the packet-count rekey trigger (spec §11).
|
||||||
client.epoch_packets = client.epoch_packets.saturating_add(1);
|
client.epoch_packets = client.epoch_packets.saturating_add(1);
|
||||||
// Only materialize a screen snapshot when this client has fallen too
|
// Live terminal bytes are never rewritten into vt100 snapshots. A
|
||||||
// far behind; the common case sends the raw output bytes and must not
|
// snapshot is useful for attach/resume, but substituting it during a
|
||||||
// clone the whole vt100 grid per client per packet.
|
// running TUI can lose graph/background-cell details that apps like
|
||||||
let (bytes, snapshot) = if client.pending.len() >= retransmit_window {
|
// btop rely on. If retransmit history is full, prune only history.
|
||||||
client.pending.clear();
|
while client.pending.len() >= retransmit_window {
|
||||||
(screen_snapshot(session.parser.screen()), true)
|
client.pending.pop_front();
|
||||||
} else {
|
}
|
||||||
(output.bytes.clone(), false)
|
|
||||||
};
|
|
||||||
let frame = Frame {
|
let frame = Frame {
|
||||||
session: output.session.clone(),
|
session: output.session.clone(),
|
||||||
output_seq,
|
output_seq,
|
||||||
bytes,
|
bytes: output.bytes.clone(),
|
||||||
snapshot,
|
snapshot: false,
|
||||||
closed: false,
|
closed: false,
|
||||||
};
|
};
|
||||||
let body = protocol::to_body(&frame)?;
|
let body = protocol::to_body(&frame)?;
|
||||||
@@ -3400,9 +3398,6 @@ async fn broadcast_output(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
&body,
|
&body,
|
||||||
)?;
|
)?;
|
||||||
while client.pending.len() >= retransmit_window {
|
|
||||||
client.pending.pop_front();
|
|
||||||
}
|
|
||||||
client.pending.push_back(PendingFrame {
|
client.pending.push_back(PendingFrame {
|
||||||
output_seq,
|
output_seq,
|
||||||
packet: packet.clone(),
|
packet: packet.clone(),
|
||||||
|
|||||||
@@ -112,6 +112,21 @@ fn write_server_config_with_output_interval(
|
|||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_server_config_with_retransmit_window(
|
||||||
|
dir: &tempfile::TempDir,
|
||||||
|
port: u16,
|
||||||
|
retransmit_window: u64,
|
||||||
|
) -> std::path::PathBuf {
|
||||||
|
let config = write_server_config(dir, port);
|
||||||
|
let mut raw = fs::read_to_string(&config).unwrap();
|
||||||
|
raw = raw.replace(
|
||||||
|
"retransmit_window = 256\n",
|
||||||
|
&format!("retransmit_window = {retransmit_window}\n"),
|
||||||
|
);
|
||||||
|
fs::write(&config, raw).unwrap();
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
fn start_server(dir: &tempfile::TempDir, config: &std::path::Path) -> Child {
|
fn start_server(dir: &tempfile::TempDir, config: &std::path::Path) -> Child {
|
||||||
let server = env!("CARGO_BIN_EXE_dosh-server");
|
let server = env!("CARGO_BIN_EXE_dosh-server");
|
||||||
let child = Command::new(server)
|
let child = Command::new(server)
|
||||||
@@ -1694,6 +1709,69 @@ fn tui_graph_glyphs_survive_fast_repaints_without_snapshot_substitution() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn btop_style_graph_output_stays_raw_when_retransmit_history_overflows() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let port = free_udp_port();
|
||||||
|
let config = write_server_config_with_retransmit_window(&dir, port, 3);
|
||||||
|
let mut server = start_server(&dir, &config);
|
||||||
|
let (socket, bootstrap, ok) = direct_attach(&config, port, "read-write");
|
||||||
|
|
||||||
|
let graph = "DOSH_BTOP_NET ⠀⠁⠃⠇⡇⣇⣧⣷⣿";
|
||||||
|
let input = Input {
|
||||||
|
bytes: format!(
|
||||||
|
"stty -echo; \
|
||||||
|
printf '\\033[?1049h\\033[?2026h\\033[?25l'; \
|
||||||
|
for i in 1 2 3 4 5 6 7 8 9 10; do \
|
||||||
|
printf '\\033[6;4H\\033[38;2;80;220;140m{} %s\\033[0m' \"$i\"; \
|
||||||
|
done; \
|
||||||
|
printf '\\033[?25h\\033[?2026l\\033[?1049l'\n",
|
||||||
|
graph
|
||||||
|
)
|
||||||
|
.into_bytes(),
|
||||||
|
};
|
||||||
|
send_encrypted(
|
||||||
|
&socket,
|
||||||
|
port,
|
||||||
|
PacketKind::Input,
|
||||||
|
ok.client_id,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
&bootstrap.session_key,
|
||||||
|
&protocol::to_body(&input).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut text = String::new();
|
||||||
|
let mut snapshots = 0usize;
|
||||||
|
let deadline = std::time::Instant::now() + Duration::from_secs(3);
|
||||||
|
while std::time::Instant::now() < deadline
|
||||||
|
&& !(text.matches("DOSH_BTOP_NET").count() >= 10 && text.contains("\x1b[?2026l"))
|
||||||
|
{
|
||||||
|
if let Some((_header, frame)) = recv_frame(&socket, &bootstrap.session_key) {
|
||||||
|
if frame.snapshot {
|
||||||
|
snapshots += 1;
|
||||||
|
}
|
||||||
|
text.push_str(&String::from_utf8_lossy(&frame.bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = server.kill();
|
||||||
|
let _ = server.wait();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
snapshots, 0,
|
||||||
|
"live btop-style graph output must not be replaced by snapshots; got {snapshots}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
text.matches("DOSH_BTOP_NET").count() >= 10
|
||||||
|
&& text.contains("⣿")
|
||||||
|
&& text.contains("\x1b[38;2;80;220;140m")
|
||||||
|
&& text.contains("\x1b[?2026h")
|
||||||
|
&& text.contains("\x1b[?2026l"),
|
||||||
|
"expected raw btop-style graph output to survive under retransmit pressure, got {text:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn large_tui_paint_is_delivered_in_mtu_safe_frames() {
|
fn large_tui_paint_is_delivered_in_mtu_safe_frames() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user