use crate::ssp::SyncState;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InputEvent {
Byte(u8),
Resize { rows: u16, cols: u16 },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum WireEvent {
Keys(Vec<u8>),
Resize { rows: u16, cols: u16 },
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct UserInput {
events: Vec<InputEvent>,
}
impl UserInput {
pub fn new() -> Self {
Self::default()
}
pub fn push_byte(&mut self, b: u8) {
self.events.push(InputEvent::Byte(b));
}
pub fn push_bytes(&mut self, bytes: &[u8]) {
self.events
.extend(bytes.iter().copied().map(InputEvent::Byte));
}
pub fn push_resize(&mut self, rows: u16, cols: u16) {
self.events.push(InputEvent::Resize { rows, cols });
}
pub fn events(&self) -> &[InputEvent] {
&self.events
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
fn coalesce(tail: &[InputEvent]) -> Vec<WireEvent> {
let mut out: Vec<WireEvent> = Vec::new();
for e in tail {
match e {
InputEvent::Byte(b) => {
if let Some(WireEvent::Keys(buf)) = out.last_mut() {
buf.push(*b);
} else {
out.push(WireEvent::Keys(vec![*b]));
}
}
InputEvent::Resize { rows, cols } => out.push(WireEvent::Resize {
rows: *rows,
cols: *cols,
}),
}
}
out
}
fn common_prefix_len(a: &[InputEvent], b: &[InputEvent]) -> usize {
a.iter().zip(b).take_while(|(x, y)| x == y).count()
}
impl SyncState for UserInput {
type Diff = Vec<WireEvent>;
fn diff_from(&self, base: &Self) -> Self::Diff {
let n = common_prefix_len(&self.events, &base.events);
coalesce(self.events.get(n..).unwrap_or_default())
}
fn apply(&mut self, diff: &Self::Diff) {
for w in diff {
match w {
WireEvent::Keys(bytes) => self.push_bytes(bytes),
WireEvent::Resize { rows, cols } => self.push_resize(*rows, *cols),
}
}
}
fn subtract_prefix(&mut self, prefix: &Self) {
let n = common_prefix_len(&self.events, &prefix.events);
self.events.drain(0..n);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ssp::testkit::{LinkParams, SimHarness};
#[test]
fn diff_apply_roundtrip() {
let mut base = UserInput::new();
base.push_bytes(b"ls -la");
let mut target = base.clone();
target.push_resize(40, 120);
target.push_bytes(b"\rcd /");
let diff = target.diff_from(&base);
let mut c = base.clone();
c.apply(&diff);
assert_eq!(c, target);
}
#[test]
fn diff_coalesces_runs() {
let mut ui = UserInput::new();
ui.push_bytes(b"abc");
ui.push_resize(10, 10);
ui.push_bytes(b"de");
let diff = ui.diff_from(&UserInput::new());
assert_eq!(
diff,
vec![
WireEvent::Keys(b"abc".to_vec()),
WireEvent::Resize { rows: 10, cols: 10 },
WireEvent::Keys(b"de".to_vec()),
]
);
}
#[test]
fn subtract_prefix_collapses() {
let mut ui = UserInput::new();
ui.push_bytes(b"hello");
let mut acked = UserInput::new();
acked.push_bytes(b"hel");
ui.subtract_prefix(&acked);
assert_eq!(
ui.events(),
&[InputEvent::Byte(b'l'), InputEvent::Byte(b'o')]
);
}
#[test]
fn diff_from_divergent_base_is_robust_and_keeps_the_tail() {
let mut target = UserInput::new();
target.push_bytes(b"abd"); let mut base = UserInput::new();
base.push_bytes(b"abc"); assert_eq!(
target.diff_from(&base),
vec![WireEvent::Keys(b"d".to_vec())]
);
let mut prefix = UserInput::new();
prefix.push_bytes(b"ab");
assert_eq!(
target.diff_from(&prefix),
vec![WireEvent::Keys(b"d".to_vec())]
);
}
#[test]
fn subtract_prefix_divergent_keeps_divergent_tail() {
let mut s = UserInput::new();
s.push_bytes(b"abd");
let mut other = UserInput::new();
other.push_bytes(b"abc");
s.subtract_prefix(&other); assert_eq!(s.events(), &[InputEvent::Byte(b'd')]);
}
#[test]
fn converges_over_lossy_link() {
let mut h = SimHarness::<UserInput, UserInput>::new(LinkParams::lossy(), 2024, 1200);
let mut typed = UserInput::new();
for round in 0..40u8 {
h.a_mut().push_byte(b'a' + (round % 26));
typed.push_byte(b'a' + (round % 26));
if round % 7 == 0 {
h.a_mut().push_resize(20 + u16::from(round), 80);
typed.push_resize(20 + u16::from(round), 80);
}
h.run_steps(6);
}
let expected = typed.events().to_vec();
h.run_until(20_000, move |h| {
h.b_view_of_a().events() == expected.as_slice()
});
}
#[test]
fn server_drains_input_incrementally() {
let mut h = SimHarness::<UserInput, UserInput>::new(LinkParams::perfect(), 5, 1200);
let mut reconstructed: Vec<u8> = Vec::new();
h.a_mut().push_bytes(b"echo hi"); h.run_until(2000, |h| h.b_view_of_a().len() >= 7);
for w in h.b.get_remote_diff() {
if let WireEvent::Keys(b) = w {
reconstructed.extend_from_slice(&b);
}
}
assert_eq!(reconstructed, b"echo hi");
h.a_mut().push_bytes(b"\rwhoami"); h.run_until(2000, |h| h.b_view_of_a().len() >= 14);
for w in h.b.get_remote_diff() {
if let WireEvent::Keys(b) = w {
reconstructed.extend_from_slice(&b);
}
}
assert_eq!(reconstructed, b"echo hi\rwhoami");
}
}