Implement NEW-ENVIRON / MNES
This commit is contained in:
		
							parent
							
								
									de00a4de66
								
							
						
					
					
						commit
						ad3fd0207e
					
				@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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"))?;
 | 
			
		||||
 | 
			
		||||
@ -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)]
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user