1227 lines
38 KiB
Rust
1227 lines
38 KiB
Rust
use piccolo::{Context, Table, Value};
|
|
use serde::{Deserialize, Serialize};
|
|
use wasm_bindgen::JsValue;
|
|
use web_sys::console;
|
|
|
|
use crate::{
|
|
telnet::{DO, DONT, ENDSUB, IAC, STARTSUB, WILL, WONT},
|
|
websocket::{send_message_to_mud, WebSocketId},
|
|
GlobalMemoCell,
|
|
};
|
|
|
|
// These enums use the terminology of RFC1143.
|
|
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
|
|
pub enum OptionState {
|
|
No,
|
|
WantNo,
|
|
WantNoOpposite,
|
|
WantYes,
|
|
WantYesOpposite,
|
|
Yes,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
pub enum Side {
|
|
Us,
|
|
Him,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Clone)]
|
|
pub struct Telopt(pub u8);
|
|
|
|
#[derive(Clone)]
|
|
pub struct MudWithMemo {
|
|
pub memo: GlobalMemoCell,
|
|
pub mud: WebSocketId,
|
|
}
|
|
|
|
pub trait SendRaw {
|
|
fn send_bytes(&mut self, msg: &[u8]);
|
|
}
|
|
impl SendRaw for MudWithMemo {
|
|
fn send_bytes(&mut self, msg: &[u8]) {
|
|
let _ = send_message_to_mud(&self.mud, msg, &self.memo);
|
|
}
|
|
}
|
|
|
|
pub trait SendOptNeg {
|
|
fn send_will(&mut self, opt: &Telopt);
|
|
fn send_wont(&mut self, opt: &Telopt);
|
|
fn send_do(&mut self, opt: &Telopt);
|
|
fn send_dont(&mut self, opt: &Telopt);
|
|
}
|
|
impl<T: SendRaw> SendOptNeg for T {
|
|
fn send_will(&mut self, opt: &Telopt) {
|
|
self.send_bytes(&[IAC, WILL, opt.0]);
|
|
}
|
|
fn send_wont(&mut self, opt: &Telopt) {
|
|
self.send_bytes(&[IAC, WONT, opt.0]);
|
|
}
|
|
fn send_do(&mut self, opt: &Telopt) {
|
|
self.send_bytes(&[IAC, DO, opt.0]);
|
|
}
|
|
fn send_dont(&mut self, opt: &Telopt) {
|
|
self.send_bytes(&[IAC, DONT, opt.0]);
|
|
}
|
|
}
|
|
|
|
pub struct OptionWithActiveSide {
|
|
option_active_on: Side,
|
|
option: Telopt,
|
|
}
|
|
|
|
fn send_positive<T: SendOptNeg>(mud: &mut T, opt: &OptionWithActiveSide) {
|
|
match opt.option_active_on {
|
|
Side::Him => mud.send_do(&opt.option),
|
|
Side::Us => mud.send_will(&opt.option),
|
|
}
|
|
}
|
|
fn send_negative<T: SendOptNeg>(mud: &mut T, opt: &OptionWithActiveSide) {
|
|
match opt.option_active_on {
|
|
Side::Him => mud.send_dont(&opt.option),
|
|
Side::Us => mud.send_wont(&opt.option),
|
|
}
|
|
}
|
|
|
|
pub fn get_option_supported<'gc>(
|
|
ctx: Context<'gc>,
|
|
input: &Table<'gc>,
|
|
opt: &Telopt,
|
|
side: &Side,
|
|
) -> bool {
|
|
let support_table: Table<'gc> = match input.get(
|
|
ctx,
|
|
match side {
|
|
Side::Us => "suppported_options_local",
|
|
Side::Him => "supported_options_remote",
|
|
},
|
|
) {
|
|
Err(_) => return false,
|
|
Ok(v) => v,
|
|
};
|
|
|
|
for (_k, v) in support_table.iter() {
|
|
if v.to_integer() == Some(opt.0 as i64) {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn set_option_supported<'gc>(ctx: Context<'gc>, input: &Table<'gc>, opt: &Telopt, side: &Side) {
|
|
let table_name = match side {
|
|
Side::Us => "suppported_options_local",
|
|
Side::Him => "supported_options_remote",
|
|
};
|
|
let support_table: Table<'gc> = match input.get(ctx, table_name) {
|
|
Err(_) => {
|
|
let t = Table::new(&ctx);
|
|
let _ = input.set(ctx, table_name, t);
|
|
t
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
|
|
for (_k, v) in support_table.iter() {
|
|
if v.to_integer() == Some(opt.0 as i64) {
|
|
return;
|
|
}
|
|
}
|
|
let _ = support_table.set(ctx, opt.0, opt.0);
|
|
}
|
|
|
|
pub fn set_option_unsupported<'gc>(
|
|
ctx: Context<'gc>,
|
|
input: &Table<'gc>,
|
|
opt: &Telopt,
|
|
side: &Side,
|
|
) {
|
|
let table_name = match side {
|
|
Side::Us => "suppported_options_local",
|
|
Side::Him => "supported_options_remote",
|
|
};
|
|
let support_table: Table<'gc> = match input.get(ctx, table_name) {
|
|
Err(_) => {
|
|
let t = Table::new(&ctx);
|
|
let _ = input.set(ctx, table_name, t);
|
|
t
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
|
|
for (k, v) in support_table.iter() {
|
|
if v.to_integer() == Some(opt.0 as i64) {
|
|
let _ = support_table.set(ctx, k, Value::Nil);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_option_state<'gc>(
|
|
ctx: Context<'gc>,
|
|
input: &Table<'gc>,
|
|
opt: &Telopt,
|
|
side: &Side,
|
|
) -> OptionState {
|
|
let state_table: Table<'gc> = match input.get(
|
|
ctx,
|
|
match side {
|
|
Side::Us => "negotiation_state_local",
|
|
Side::Him => "negotiation_state_remote",
|
|
},
|
|
) {
|
|
Err(_) => return OptionState::No,
|
|
Ok(v) => v,
|
|
};
|
|
let v: String = match state_table.get(ctx, opt.0) {
|
|
Err(_) => return OptionState::No,
|
|
Ok(v) => v,
|
|
};
|
|
match serde_json::from_value(serde_json::Value::String(v)) {
|
|
Err(_) => OptionState::No,
|
|
Ok(v) => v,
|
|
}
|
|
}
|
|
|
|
pub fn send_subnegotiation_if_allowed<'gc, T: SendRaw>(
|
|
ctx: Context<'gc>,
|
|
mud_table: &Table<'gc>,
|
|
opt: &Telopt,
|
|
side: &Side,
|
|
mud: &mut T,
|
|
msg: &[u8],
|
|
) {
|
|
if get_option_state(ctx, mud_table, opt, side) == OptionState::Yes {
|
|
let mut buf: Vec<u8> = Vec::with_capacity(msg.len() + 4);
|
|
buf.extend_from_slice(&[IAC, STARTSUB]);
|
|
for c in msg {
|
|
if *c == IAC {
|
|
buf.extend_from_slice(b"\xff\xff");
|
|
} else {
|
|
buf.push(*c);
|
|
}
|
|
}
|
|
buf.extend_from_slice(&[IAC, ENDSUB]);
|
|
mud.send_bytes(&buf);
|
|
}
|
|
}
|
|
|
|
pub fn set_option_state<'gc>(
|
|
ctx: Context<'gc>,
|
|
input: &Table<'gc>,
|
|
opt: &Telopt,
|
|
side: &Side,
|
|
state: &OptionState,
|
|
) {
|
|
let table_name = match side {
|
|
Side::Us => "negotiation_state_local",
|
|
Side::Him => "negotiation_state_remote",
|
|
};
|
|
let state_table: Table<'gc> = match input.get(ctx, table_name) {
|
|
Err(_) => {
|
|
let t = Table::new(&ctx);
|
|
let _ = input.set(ctx, table_name, t);
|
|
t
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
let json_val = serde_json::to_value(state).expect("couldn't serialize state");
|
|
let _ = state_table.set(
|
|
ctx,
|
|
opt.0,
|
|
json_val.as_str().expect("state wasn't a string").to_owned(),
|
|
);
|
|
}
|
|
|
|
fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
opt: &OptionWithActiveSide,
|
|
) -> bool {
|
|
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
|
OptionState::No => {
|
|
if get_option_supported(ctx, table, &opt.option, &opt.option_active_on) {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::Yes,
|
|
);
|
|
send_positive(mud, opt);
|
|
return true;
|
|
} else {
|
|
send_negative(mud, opt);
|
|
}
|
|
}
|
|
OptionState::Yes => {}
|
|
OptionState::WantNo => {
|
|
console::log_1(&JsValue::from_str(&format!(
|
|
"DONT answered by WILL for option {}",
|
|
opt.option.0
|
|
)));
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::No,
|
|
)
|
|
}
|
|
OptionState::WantNoOpposite => {
|
|
console::log_1(&JsValue::from_str(&format!(
|
|
"DONT answered by WILL for option {}",
|
|
opt.option.0
|
|
)));
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::Yes,
|
|
);
|
|
return true;
|
|
}
|
|
OptionState::WantYes => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::Yes,
|
|
);
|
|
return true;
|
|
}
|
|
OptionState::WantYesOpposite => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantNo,
|
|
);
|
|
send_negative(mud, opt);
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn handle_incoming_negative<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
opt: &OptionWithActiveSide,
|
|
) {
|
|
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
|
OptionState::No => {}
|
|
OptionState::Yes => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::No,
|
|
);
|
|
send_negative(mud, opt)
|
|
}
|
|
OptionState::WantNo => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::No,
|
|
),
|
|
OptionState::WantNoOpposite => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantYes,
|
|
);
|
|
send_positive(mud, opt);
|
|
}
|
|
OptionState::WantYes => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::No,
|
|
),
|
|
OptionState::WantYesOpposite => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::No,
|
|
),
|
|
}
|
|
}
|
|
|
|
pub fn handle_incoming_will<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
option: &Telopt,
|
|
) -> bool {
|
|
handle_incoming_positive(
|
|
ctx,
|
|
mud,
|
|
table,
|
|
&OptionWithActiveSide {
|
|
option: option.clone(),
|
|
option_active_on: Side::Him,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn handle_incoming_wont<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
option: &Telopt,
|
|
) {
|
|
handle_incoming_negative(
|
|
ctx,
|
|
mud,
|
|
table,
|
|
&OptionWithActiveSide {
|
|
option: option.clone(),
|
|
option_active_on: Side::Him,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub fn handle_incoming_do<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
option: &Telopt,
|
|
) -> bool {
|
|
handle_incoming_positive(
|
|
ctx,
|
|
mud,
|
|
table,
|
|
&OptionWithActiveSide {
|
|
option: option.clone(),
|
|
option_active_on: Side::Us,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn handle_incoming_dont<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
option: &Telopt,
|
|
) {
|
|
handle_incoming_negative(
|
|
ctx,
|
|
mud,
|
|
table,
|
|
&OptionWithActiveSide {
|
|
option: option.clone(),
|
|
option_active_on: Side::Us,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub const GMCP_TELOPT: Telopt = Telopt(201);
|
|
pub const NAWS_TELOPT: Telopt = Telopt(31);
|
|
|
|
fn negotiate_option_on<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
opt: &OptionWithActiveSide,
|
|
) {
|
|
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
|
OptionState::No => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantYes,
|
|
);
|
|
send_positive(mud, opt);
|
|
}
|
|
OptionState::Yes => {}
|
|
OptionState::WantNo => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantNoOpposite,
|
|
),
|
|
OptionState::WantNoOpposite => {}
|
|
OptionState::WantYes => {}
|
|
OptionState::WantYesOpposite => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantYes,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn negotiate_option_off<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
opt: &OptionWithActiveSide,
|
|
) {
|
|
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
|
OptionState::No => {}
|
|
OptionState::Yes => {
|
|
set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantNo,
|
|
);
|
|
send_negative(mud, opt);
|
|
}
|
|
OptionState::WantNo => {}
|
|
OptionState::WantNoOpposite => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantNo,
|
|
),
|
|
OptionState::WantYes => set_option_state(
|
|
ctx,
|
|
table,
|
|
&opt.option,
|
|
&opt.option_active_on,
|
|
&OptionState::WantYesOpposite,
|
|
),
|
|
OptionState::WantYesOpposite => {}
|
|
}
|
|
}
|
|
|
|
pub fn negotiate_option<'gc, T: SendOptNeg>(
|
|
ctx: Context<'gc>,
|
|
mud: &mut T,
|
|
table: &Table<'gc>,
|
|
opt: &OptionWithActiveSide,
|
|
desired_state: bool,
|
|
) {
|
|
if desired_state {
|
|
negotiate_option_on(ctx, mud, table, opt);
|
|
} else {
|
|
negotiate_option_off(ctx, mud, table, opt);
|
|
}
|
|
}
|
|
|
|
pub fn configure_telopt_table<'gc>(ctx: Context<'gc>, table: &Table<'gc>) {
|
|
table
|
|
.set(ctx, "naws", NAWS_TELOPT.0)
|
|
.expect("Can't set NAWS in telopt table");
|
|
table
|
|
.set(ctx, "gmcp", GMCP_TELOPT.0)
|
|
.expect("Can't set GMCP in telopt table");
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use piccolo::{Context, Lua, Table};
|
|
|
|
use crate::{
|
|
lua_engine::telopt::{set_option_state, OptionState, Telopt},
|
|
telnet::{parse_telnet_buf, TelnetOutput},
|
|
};
|
|
|
|
use super::{
|
|
get_option_state, handle_incoming_do, handle_incoming_dont, handle_incoming_will,
|
|
handle_incoming_wont, negotiate_option, set_option_supported, OptionWithActiveSide,
|
|
SendRaw, Side, GMCP_TELOPT,
|
|
};
|
|
|
|
#[test]
|
|
fn get_option_defaults_no() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
let root_tab = Table::new(&ctx);
|
|
assert_eq!(
|
|
get_option_state(ctx, &root_tab, &GMCP_TELOPT, &Side::Him),
|
|
OptionState::No
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn get_set_option_state_roundtrips() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
let root_tab = Table::new(&ctx);
|
|
set_option_state(ctx, &root_tab, &GMCP_TELOPT, &Side::Him, &OptionState::Yes);
|
|
set_option_state(
|
|
ctx,
|
|
&root_tab,
|
|
&GMCP_TELOPT,
|
|
&Side::Us,
|
|
&OptionState::WantNoOpposite,
|
|
);
|
|
set_option_state(
|
|
ctx,
|
|
&root_tab,
|
|
&Telopt(123),
|
|
&Side::Us,
|
|
&OptionState::WantYes,
|
|
);
|
|
assert_eq!(
|
|
get_option_state(ctx, &root_tab, &GMCP_TELOPT, &Side::Him),
|
|
OptionState::Yes
|
|
);
|
|
assert_eq!(
|
|
get_option_state(ctx, &root_tab, &GMCP_TELOPT, &Side::Us),
|
|
OptionState::WantNoOpposite
|
|
);
|
|
assert_eq!(
|
|
get_option_state(ctx, &root_tab, &Telopt(123), &Side::Us),
|
|
OptionState::WantYes
|
|
);
|
|
});
|
|
}
|
|
|
|
pub enum SideTestAction {
|
|
SetOptionSupported {
|
|
option: OptionWithActiveSide,
|
|
},
|
|
SetOptionEnabled {
|
|
state: bool,
|
|
option: OptionWithActiveSide,
|
|
},
|
|
AssertOptionState {
|
|
expected_state: OptionState,
|
|
option: OptionWithActiveSide,
|
|
},
|
|
}
|
|
|
|
pub enum TestEvent {
|
|
SideAAction(SideTestAction),
|
|
SideBAction(SideTestAction),
|
|
SyncUntilStatic,
|
|
}
|
|
|
|
pub struct FakeMudBuffer(Vec<u8>);
|
|
impl SendRaw for FakeMudBuffer {
|
|
fn send_bytes(&mut self, msg: &[u8]) {
|
|
self.0.extend_from_slice(msg);
|
|
}
|
|
}
|
|
|
|
pub struct TwoPartySim<'gc> {
|
|
table_a: Table<'gc>,
|
|
table_b: Table<'gc>,
|
|
buffer_a: FakeMudBuffer,
|
|
buffer_b: FakeMudBuffer,
|
|
}
|
|
|
|
fn run_test_event<'gc>(ctx: Context<'gc>, sim_state: &mut TwoPartySim<'gc>, event: TestEvent) {
|
|
match event {
|
|
TestEvent::SideAAction(act) => {
|
|
run_side_action(ctx, act, sim_state.table_a, &mut sim_state.buffer_a)
|
|
}
|
|
TestEvent::SideBAction(act) => {
|
|
run_side_action(ctx, act, sim_state.table_b, &mut sim_state.buffer_b)
|
|
}
|
|
TestEvent::SyncUntilStatic => sync_until_static(ctx, sim_state),
|
|
}
|
|
}
|
|
|
|
fn run_simulation<'gc>(ctx: Context<'gc>, simulation: Vec<TestEvent>) {
|
|
let mut state = TwoPartySim {
|
|
table_a: Table::new(&ctx),
|
|
table_b: Table::new(&ctx),
|
|
buffer_a: FakeMudBuffer(Vec::new()),
|
|
buffer_b: FakeMudBuffer(Vec::new()),
|
|
};
|
|
for event in simulation {
|
|
run_test_event(ctx, &mut state, event);
|
|
}
|
|
}
|
|
|
|
fn run_side_action<'gc>(
|
|
ctx: Context<'gc>,
|
|
act: SideTestAction,
|
|
table: Table<'gc>,
|
|
buffer: &mut FakeMudBuffer,
|
|
) {
|
|
match act {
|
|
SideTestAction::SetOptionEnabled { state, option } => {
|
|
negotiate_option(ctx, buffer, &table, &option, state)
|
|
}
|
|
SideTestAction::AssertOptionState {
|
|
expected_state,
|
|
option,
|
|
} => {
|
|
assert_eq!(
|
|
get_option_state(ctx, &table, &option.option, &option.option_active_on),
|
|
expected_state
|
|
);
|
|
}
|
|
SideTestAction::SetOptionSupported { option } => {
|
|
set_option_supported(ctx, &table, &option.option, &option.option_active_on)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn sync_until_static<'gc>(ctx: Context<'gc>, sim_state: &mut TwoPartySim<'gc>) {
|
|
loop {
|
|
if sim_state.buffer_a.0.is_empty() && sim_state.buffer_b.0.is_empty() {
|
|
break;
|
|
}
|
|
let (remaining, msg_a_to_b) = parse_telnet_buf(&sim_state.buffer_a.0);
|
|
sim_state.buffer_a.0 = remaining;
|
|
let (remaining, msg_b_to_a) = parse_telnet_buf(&sim_state.buffer_b.0);
|
|
sim_state.buffer_b.0 = remaining;
|
|
|
|
println!(
|
|
"Message exchange: A->B: {:#?}, B->A: {:#?}",
|
|
&msg_a_to_b, &msg_b_to_a
|
|
);
|
|
|
|
simulate_msg_to(ctx, msg_a_to_b, sim_state.table_b, &mut sim_state.buffer_b);
|
|
simulate_msg_to(ctx, msg_b_to_a, sim_state.table_a, &mut sim_state.buffer_a);
|
|
}
|
|
}
|
|
|
|
fn simulate_msg_to<'gc>(
|
|
ctx: Context<'gc>,
|
|
msg: Option<TelnetOutput>,
|
|
table: Table<'gc>,
|
|
buffer: &mut FakeMudBuffer,
|
|
) {
|
|
match msg {
|
|
Some(TelnetOutput::Will(n)) => handle_incoming_will(ctx, buffer, &table, &Telopt(n)),
|
|
Some(TelnetOutput::Wont(n)) => handle_incoming_wont(ctx, buffer, &table, &Telopt(n)),
|
|
Some(TelnetOutput::Do(n)) => handle_incoming_do(ctx, buffer, &table, &Telopt(n)),
|
|
Some(TelnetOutput::Dont(n)) => handle_incoming_dont(ctx, buffer, &table, &Telopt(n)),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
use SideTestAction::*;
|
|
use TestEvent::*;
|
|
|
|
#[test]
|
|
fn negotiating_unsupported_local_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_unsupported_remote_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_bothways_local_converges_both_on() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_converges_both_on() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionSupported {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_then_changing_locally_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: false,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_then_changing_remotely_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: false,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_and_changing_locally_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: false,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_and_changing_remotely_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: false,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_and_changing_remotely_twice_converges_both_on() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: false,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_and_changing_locally_once_remotely_twice_converges_both_off() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: false,
|
|
}),
|
|
SideBAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
state: true,
|
|
}),
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: false,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::No,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn negotiating_supported_local_then_changing_locally_and_reversing_converges_both_on() {
|
|
let mut lua = Lua::empty();
|
|
lua.enter(|ctx| {
|
|
run_simulation(
|
|
ctx,
|
|
vec![
|
|
SideBAction(SetOptionSupported {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
}),
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: false,
|
|
}),
|
|
SideAAction(SetOptionEnabled {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
state: true,
|
|
}),
|
|
SyncUntilStatic,
|
|
SideAAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Us,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
SideBAction(AssertOptionState {
|
|
option: OptionWithActiveSide {
|
|
option: Telopt(123),
|
|
option_active_on: Side::Him,
|
|
},
|
|
expected_state: OptionState::Yes,
|
|
}),
|
|
],
|
|
)
|
|
});
|
|
}
|
|
}
|