From ad3fd0207edecf4e00b63dffd32a595f2d26737f Mon Sep 17 00:00:00 2001 From: Condorra Date: Sun, 24 Nov 2024 00:32:37 +1100 Subject: [PATCH] Implement NEW-ENVIRON / MNES --- src/lua_engine.rs | 6 ++ src/lua_engine/frames.rs | 2 - src/lua_engine/muds.rs | 171 +++++++++++++++++++++++++++++++++- src/lua_engine/muds/telopt.rs | 4 + 4 files changed, 177 insertions(+), 6 deletions(-) diff --git a/src/lua_engine.rs b/src/lua_engine.rs index 7adef36..74e8f48 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -318,6 +318,7 @@ pub fn install_lua_globals( register_class_function!(mud_class_table, mudoutput_dont); register_stateless_class_function!(mud_class_table, mudoutput_subnegotiation); register_class_function!(mud_class_table, mudoutput_subnegotiation_termtype); + register_class_function!(mud_class_table, mudoutput_subnegotiation_environ); register_class_function!(mud_class_table, mudinput_line); register_class_function!(mud_class_table, "closed", mudclass_closed); register_stateless_class_function!(mud_class_table, "new", new_mud); @@ -328,6 +329,11 @@ pub fn install_lua_globals( "local_termtype_enabled", opt_enabled_noop ); + register_stateless_class_function!( + mud_class_table, + "local_environ_enabled", + opt_enabled_noop + ); register_stateless_class_function!( mud_class_table, "remote_eor_enabled", diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index e473d73..7b73dd1 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -12,8 +12,6 @@ use piccolo::{ SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, Variadic, }; use std::{rc::Rc, str}; -use wasm_bindgen::JsValue; -use web_sys::console; use yew::UseStateSetter; use super::call_checking_metatable; diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs index 889576b..abf1127 100644 --- a/src/lua_engine/muds.rs +++ b/src/lua_engine/muds.rs @@ -1,10 +1,13 @@ -use anyhow::Error; +use core::str; +use std::mem::swap; + +use anyhow::{bail, Error}; use gc_arena::{Gc, Rootable}; use piccolo::{ self, async_sequence, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, }; -use telopt::{EOR_TELOPT, TERMTYPE_TELOPT}; +use telopt::{ENVIRON_TELOPT, EOR_TELOPT, TERMTYPE_TELOPT}; use wasm_bindgen::JsValue; use web_sys::{console, window}; use yew::UseStateSetter; @@ -242,6 +245,7 @@ pub(super) fn connect_mud<'gc>( } set_option_supported(ctx, &conntab, &TERMTYPE_TELOPT, &Side::Us); set_option_supported(ctx, &conntab, &EOR_TELOPT, &Side::Him); + set_option_supported(ctx, &conntab, &ENVIRON_TELOPT, &Side::Us); // Call conntab:new... let seq = async_sequence(&ctx, |locals, mut seq| { @@ -698,11 +702,14 @@ pub(super) fn mudoutput_subnegotiation_termtype<'gc>( }; let supported_termtypes: Table = mud.get(ctx, "supported_termtypes")?; let optlen = supported_termtypes.length() as u64; - if negidx > optlen { + if negidx > optlen + 1 { negidx = 1; } - let termtype: String = supported_termtypes.get(ctx, negidx as i64)?; + // We repeat the last one before cycling. + let effective_negidx = if negidx > optlen { optlen } else { negidx }; + + let termtype: String = supported_termtypes.get(ctx, effective_negidx as i64)?; send_subnegotiation_if_allowed( ctx, @@ -725,6 +732,154 @@ pub(super) fn mudoutput_subnegotiation_termtype<'gc>( }) } +fn parse_environ_sendcmd(mut input: &[u8]) -> anyhow::Result>> { + let mut args: Vec> = vec![]; + let mut curstr: Vec = vec![]; + + const VAR: u8 = 0; + const ESC: u8 = 2; + const USERVAR: u8 = 3; + loop { + match input.first() { + None => { + if !curstr.is_empty() { + args.push(curstr); + } + return Ok(args); + } + Some(c) => { + input = &input[1..]; + match *c { + ESC => match input.first() { + None => { + bail!("new-environ SEND command ended with escape, which is invalid"); + } + Some(c) => { + input = &input[1..]; + curstr.push(*c); + } + }, + VAR | USERVAR => { + if !curstr.is_empty() { + let mut laststr: Vec = vec![]; + swap(&mut curstr, &mut laststr); + args.push(laststr); + } + } + _ => curstr.push(*c), + } + } + } + } +} + +fn escape_environ(input: &[u8]) -> Vec { + let mut buf: Vec = vec![]; + const VAR: u8 = 0; + const VALUE: u8 = 1; + const ESC: u8 = 2; + const USERVAR: u8 = 3; + for c in input { + match *c { + VAR => { + buf.push(ESC); + buf.push(VAR); + } + VALUE => { + buf.push(ESC); + buf.push(VALUE); + } + ESC => { + buf.push(ESC); + buf.push(ESC); + } + USERVAR => { + buf.push(ESC); + buf.push(USERVAR); + } + c => buf.push(c), + } + } + buf +} + +pub(super) fn mudoutput_subnegotiation_environ<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (mud, mut msg): (Table, Vec) = stack.consume(ctx)?; + if msg.is_empty() { + return Ok(CallbackReturn::Return); + } + let cmd = msg.remove(0); + + let socket: Value = mud.get(ctx, "socket")?; + let socket: &WebSocketId = + UserData::from_value(ctx, socket)?.downcast::]>()?; + + let environ: Table = mud.get(ctx, "environ_for_remote")?; + + const IS_CMD: u8 = 0; + const SEND_CMD: u8 = 1; + + const VAR: u8 = 0; + const VALUE: u8 = 1; + match cmd { + SEND_CMD => { + let mut requested_env = parse_environ_sendcmd(&msg)?; + console::log_1(&JsValue::from_str(&format!( + "Environment request of length: {}", + requested_env.len() + ))); + if requested_env.is_empty() { + requested_env = environ + .iter() + .map(|(k, _v)| { + Ok::<_, anyhow::Error>( + piccolo::String::from_value(ctx, k)?.as_bytes().to_owned(), + ) + }) + .collect::>>()?; + } + console::log_1(&JsValue::from_str(&format!( + "After expansion, environment request of length: {}", + requested_env.len() + ))); + + let mut buf: Vec = vec![]; + buf.push(IS_CMD); + for env in &requested_env { + if let Ok(envstr) = environ.get::<_, piccolo::String>( + ctx, + piccolo::String::from_slice(&ctx, env.as_slice()), + ) { + buf.push(VAR); + buf.extend_from_slice(escape_environ(env).as_slice()); + buf.push(VALUE); + buf.extend_from_slice(escape_environ(envstr.as_bytes()).as_slice()); + } + } + send_subnegotiation_if_allowed( + ctx, + &mud, + &ENVIRON_TELOPT, + &Side::Us, + &mut MudWithMemo { + memo: global_memo.clone(), + mud: socket.clone(), + }, + buf.as_slice(), + ); + + Ok(CallbackReturn::Return) + } + _ => Ok(CallbackReturn::Return), + } + }) +} + pub(super) fn mudinput_line<'gc>( ctx: Context<'gc>, _global_memo: &GlobalMemoCell, @@ -796,6 +951,14 @@ pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> { termtypes.set(ctx, 3_i64, "MTTS 815")?; mud.set(ctx, ctx.intern_static(b"supported_termtypes"), termtypes)?; + let environ: Table = Table::new(&ctx); + environ.set(ctx, "CLIENT_NAME", "worldwideportal")?; + environ.set(ctx, "CLIENT_VERSION", "0.0.1")?; + environ.set(ctx, "CHARSET", "UTF-8")?; + environ.set(ctx, "MTTS", "815")?; + environ.set(ctx, "TERMINAL_TYPE", "XTERM")?; + mud.set(ctx, ctx.intern_static(b"environ_for_remote"), environ)?; + let curr_frame: Value = ctx .get_global::("info")? .get(ctx, ctx.intern_static(b"current_frame"))?; diff --git a/src/lua_engine/muds/telopt.rs b/src/lua_engine/muds/telopt.rs index 1b3a90b..44a9699 100644 --- a/src/lua_engine/muds/telopt.rs +++ b/src/lua_engine/muds/telopt.rs @@ -430,6 +430,7 @@ pub fn handle_incoming_dont<'gc, T: SendOptNeg>( pub const TERMTYPE_TELOPT: Telopt = Telopt(24); pub const EOR_TELOPT: Telopt = Telopt(25); pub const NAWS_TELOPT: Telopt = Telopt(31); +pub const ENVIRON_TELOPT: Telopt = Telopt(39); pub const GMCP_TELOPT: Telopt = Telopt(201); fn negotiate_option_on<'gc, T: SendOptNeg>( @@ -533,6 +534,9 @@ pub fn configure_telopt_table<'gc>(ctx: Context<'gc>, table: &Table<'gc>) { table .set(ctx, "termtype", TERMTYPE_TELOPT.0) .expect("Can't set TERMTYPE in telopt table"); + table + .set(ctx, "environ", ENVIRON_TELOPT.0) + .expect("Can't set ENVIRON in telopt table"); } #[cfg(test)]