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_class_function!(mud_class_table, mudoutput_dont); | ||||||
|             register_stateless_class_function!(mud_class_table, mudoutput_subnegotiation); |             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_termtype); | ||||||
|  |             register_class_function!(mud_class_table, mudoutput_subnegotiation_environ); | ||||||
|             register_class_function!(mud_class_table, mudinput_line); |             register_class_function!(mud_class_table, mudinput_line); | ||||||
|             register_class_function!(mud_class_table, "closed", mudclass_closed); |             register_class_function!(mud_class_table, "closed", mudclass_closed); | ||||||
|             register_stateless_class_function!(mud_class_table, "new", new_mud); |             register_stateless_class_function!(mud_class_table, "new", new_mud); | ||||||
| @ -328,6 +329,11 @@ pub fn install_lua_globals( | |||||||
|                 "local_termtype_enabled", |                 "local_termtype_enabled", | ||||||
|                 opt_enabled_noop |                 opt_enabled_noop | ||||||
|             ); |             ); | ||||||
|  |             register_stateless_class_function!( | ||||||
|  |                 mud_class_table, | ||||||
|  |                 "local_environ_enabled", | ||||||
|  |                 opt_enabled_noop | ||||||
|  |             ); | ||||||
|             register_stateless_class_function!( |             register_stateless_class_function!( | ||||||
|                 mud_class_table, |                 mud_class_table, | ||||||
|                 "remote_eor_enabled", |                 "remote_eor_enabled", | ||||||
|  | |||||||
| @ -12,8 +12,6 @@ use piccolo::{ | |||||||
|     SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, Variadic, |     SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, Variadic, | ||||||
| }; | }; | ||||||
| use std::{rc::Rc, str}; | use std::{rc::Rc, str}; | ||||||
| use wasm_bindgen::JsValue; |  | ||||||
| use web_sys::console; |  | ||||||
| use yew::UseStateSetter; | use yew::UseStateSetter; | ||||||
| 
 | 
 | ||||||
| use super::call_checking_metatable; | 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 gc_arena::{Gc, Rootable}; | ||||||
| use piccolo::{ | use piccolo::{ | ||||||
|     self, async_sequence, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, |     self, async_sequence, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, | ||||||
|     SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, |     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 wasm_bindgen::JsValue; | ||||||
| use web_sys::{console, window}; | use web_sys::{console, window}; | ||||||
| use yew::UseStateSetter; | 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, &TERMTYPE_TELOPT, &Side::Us); | ||||||
|         set_option_supported(ctx, &conntab, &EOR_TELOPT, &Side::Him); |         set_option_supported(ctx, &conntab, &EOR_TELOPT, &Side::Him); | ||||||
|  |         set_option_supported(ctx, &conntab, &ENVIRON_TELOPT, &Side::Us); | ||||||
| 
 | 
 | ||||||
|         // Call conntab:new...
 |         // Call conntab:new...
 | ||||||
|         let seq = async_sequence(&ctx, |locals, mut seq| { |         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 supported_termtypes: Table = mud.get(ctx, "supported_termtypes")?; | ||||||
|                 let optlen = supported_termtypes.length() as u64; |                 let optlen = supported_termtypes.length() as u64; | ||||||
|                 if negidx > optlen { |                 if negidx > optlen + 1 { | ||||||
|                     negidx = 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( |                 send_subnegotiation_if_allowed( | ||||||
|                     ctx, |                     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>( | pub(super) fn mudinput_line<'gc>( | ||||||
|     ctx: Context<'gc>, |     ctx: Context<'gc>, | ||||||
|     _global_memo: &GlobalMemoCell, |     _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")?; |         termtypes.set(ctx, 3_i64, "MTTS 815")?; | ||||||
|         mud.set(ctx, ctx.intern_static(b"supported_termtypes"), termtypes)?; |         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 |         let curr_frame: Value = ctx | ||||||
|             .get_global::<Table>("info")? |             .get_global::<Table>("info")? | ||||||
|             .get(ctx, ctx.intern_static(b"current_frame"))?; |             .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 TERMTYPE_TELOPT: Telopt = Telopt(24); | ||||||
| pub const EOR_TELOPT: Telopt = Telopt(25); | pub const EOR_TELOPT: Telopt = Telopt(25); | ||||||
| pub const NAWS_TELOPT: Telopt = Telopt(31); | pub const NAWS_TELOPT: Telopt = Telopt(31); | ||||||
|  | pub const ENVIRON_TELOPT: Telopt = Telopt(39); | ||||||
| pub const GMCP_TELOPT: Telopt = Telopt(201); | pub const GMCP_TELOPT: Telopt = Telopt(201); | ||||||
| 
 | 
 | ||||||
| fn negotiate_option_on<'gc, T: SendOptNeg>( | fn negotiate_option_on<'gc, T: SendOptNeg>( | ||||||
| @ -533,6 +534,9 @@ pub fn configure_telopt_table<'gc>(ctx: Context<'gc>, table: &Table<'gc>) { | |||||||
|     table |     table | ||||||
|         .set(ctx, "termtype", TERMTYPE_TELOPT.0) |         .set(ctx, "termtype", TERMTYPE_TELOPT.0) | ||||||
|         .expect("Can't set TERMTYPE in telopt table"); |         .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)] | #[cfg(test)] | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user