worldwideportal/src/lua_engine/muds/telopt.rs

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,
}),
],
)
});
}
}