Implement NEW-ENVIRON / MNES

This commit is contained in:
Condorra 2024-11-24 00:32:37 +11:00
parent de00a4de66
commit ad3fd0207e
4 changed files with 177 additions and 6 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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<Vec<Vec<u8>>> {
let mut args: Vec<Vec<u8>> = vec![];
let mut curstr: Vec<u8> = 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<u8> = vec![];
swap(&mut curstr, &mut laststr);
args.push(laststr);
}
}
_ => curstr.push(*c),
}
}
}
}
}
fn escape_environ(input: &[u8]) -> Vec<u8> {
let mut buf: Vec<u8> = 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<u8>) = 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::<Rootable![Gc<'_, WebSocketId>]>()?;
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::<anyhow::Result<Vec<_>>>()?;
}
console::log_1(&JsValue::from_str(&format!(
"After expansion, environment request of length: {}",
requested_env.len()
)));
let mut buf: Vec<u8> = 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::<Table>("info")?
.get(ctx, ctx.intern_static(b"current_frame"))?;

View File

@ -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)]