diff --git a/src/frame_view.rs b/src/frame_view.rs index 55cd308..f38c761 100644 --- a/src/frame_view.rs +++ b/src/frame_view.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use gc_arena::Collect; use wasm_bindgen::prelude::*; -use web_sys::{Element, Node}; +use web_sys::{console, Element, Node}; use yew::prelude::*; use crate::{ @@ -164,7 +164,15 @@ fn get_or_make_term_frame<'a>( .ok() }) }) - .unwrap_or(()) + .unwrap_or(()); + let mut engine = globals_for_resize.lua_engine.borrow_mut(); + match engine.dispatch_resize(&frame_for_resize, dims.cols(), dims.rows()) { + Err(e) => console::log_1(&JsValue::from_str(&format!( + "Error sending resize to Lua: {}", + e + ))), + Ok(()) => {} + } }); term.onResize(&resize_closure); diff --git a/src/lua_engine.rs b/src/lua_engine.rs index 6c7b4fc..6e950c8 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -1,5 +1,6 @@ use self::{frameroutes::*, frames::*, muds::*, storage::*}; use anyhow::Error; +use muds::telopt::configure_telopt_table; use piccolo::{ async_callback::{AsyncSequence, Locals}, meta_ops::{self, MetaResult}, @@ -127,6 +128,31 @@ impl LuaState { self.interp.execute(&self.exec)?; Ok(()) } + + pub(crate) fn dispatch_resize( + &mut self, + frame: &FrameId, + width: u16, + height: u16, + ) -> anyhow::Result<()> { + self.interp.try_enter(|ctx| { + let frames: Table = ctx.get_global("frames")?; + let frame_tab: Value = frames.get(ctx, frame.0 as i64)?; + if frame_tab.is_nil() { + Err(anyhow::Error::msg( + "Dispatching command to frame missing in Lua frames.", + ))?; + } + ctx.fetch(&self.exec).restart( + ctx, + Function::Callback(send_resize_to_frame(ctx)), + (frame_tab, width, height), + ); + Ok(()) + })?; + self.interp.execute(&self.exec)?; + Ok(()) + } } pub fn install_lua_globals( @@ -209,6 +235,11 @@ pub fn install_lua_globals( ctx.set_global("commands", cmd_table); let info_table = Table::new(&ctx); ctx.set_global("info", info_table); + + let telopt_table = Table::new(&ctx); + let _ = info_table.set(ctx, "telopts", telopt_table); + configure_telopt_table(ctx, &telopt_table); + let muds_table = Table::new(&ctx); ctx.set_global("muds", muds_table); let frames_table = Table::new(&ctx); @@ -284,6 +315,8 @@ pub fn install_lua_globals( 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); + register_class_function!(mud_class_table, "term_resized", mud_term_resized); + register_class_function!(mud_class_table, local_naws_enabled); macro_rules! register_class_nop { ($class_table: ident, $sym: ident) => { @@ -308,6 +341,7 @@ pub fn install_lua_globals( frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?; register_class_function!(frame_class_table, "new", new_frame); register_class_function!(frame_class_table, "input", frame_input); + register_class_function!(frame_class_table, "resize", frame_resize); let frameroute_class_table = Table::new(&ctx); classes_table.set(ctx, "frameroute", frameroute_class_table)?; @@ -384,14 +418,15 @@ pub fn prep_metaop_call<'gc, const N: usize>( } } -pub async fn call_checking_metatable<'gc, T: Fetchable>( +pub async fn call_checking_metatable<'gc, T: Fetchable, FN>( seq: &mut AsyncSequence, obj: T, - func_name: &'static str, + func_name: FN, arguments: &[StashedValue], ) -> Result<(), StashedError> where for<'gcb> ::Fetched<'gcb>: IntoValue<'gcb>, + for<'gcb> FN: IntoValue<'gcb>, { let call = seq.try_enter(|ctx, locals, _execution, mut stack| { let obj = locals.fetch(&obj); @@ -400,11 +435,7 @@ where ctx, stack, locals, - meta_ops::index( - ctx, - obj.into_value(ctx), - ctx.intern_static(func_name.as_bytes()).into_value(ctx), - )?, + meta_ops::index(ctx, obj.into_value(ctx), func_name.into_value(ctx))?, )) })?; if let Some(call) = call { diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index 788b24e..edc5ad5 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -106,7 +106,8 @@ pub fn list_match_tab<'gc>( let frame = locals.stash(&ctx, intern_id(ctx, frame)); let aliases = locals.stash(&ctx, aliases); async move { - call_checking_metatable::(&mut seq, aliases, "lua_table", &[]).await?; + call_checking_metatable::(&mut seq, aliases, "lua_table", &[]) + .await?; let echo_func = seq.try_enter(|ctx, locals, _ex, mut stack| { let alias_tab: Table = stack.consume(ctx)?; stack.push_back(locals.fetch(&frame).into_value(ctx)); @@ -321,7 +322,7 @@ pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &FrameId) -> Callbac let frame_tab = locals.stash(&ctx, frame_tab); let frame = locals.stash(&ctx, frame); async move { - call_checking_metatable::(&mut seq, frame_tab, "new", &[frame]) + call_checking_metatable::(&mut seq, frame_tab, "new", &[frame]) .await?; Ok(SequenceReturn::Return) } @@ -372,7 +373,8 @@ pub(super) fn send_command_to_frame<'gc>(ctx: Context<'gc>) -> Callback<'gc> { let frame = locals.stash(&ctx, frame); let line = locals.stash(&ctx, line.into_value(ctx)); async move { - call_checking_metatable::(&mut seq, frame, "input", &[line]).await?; + call_checking_metatable::(&mut seq, frame, "input", &[line]) + .await?; Ok(SequenceReturn::Return) } }); @@ -403,7 +405,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) let frame_tab = locals.stash(&ctx, frame_tab); let frame = locals.stash(&ctx, frame); async move { - call_checking_metatable::( + call_checking_metatable::( &mut seq, aliases.clone(), "try_run_sub", @@ -426,7 +428,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) return Ok(SequenceReturn::Return); } - call_checking_metatable::( + call_checking_metatable::( &mut seq, linked_mud, "mudinput_line", @@ -444,6 +446,77 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) }) } +pub(super) fn send_resize_to_frame<'gc>(ctx: Context<'gc>) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (frame, width, height): (Table, u16, u16) = stack.consume(ctx)?; + let seq = async_sequence(&ctx, |locals, mut seq| { + let frame = locals.stash(&ctx, frame); + let width = locals.stash(&ctx, width.into_value(ctx)); + let height = locals.stash(&ctx, height.into_value(ctx)); + async move { + call_checking_metatable::( + &mut seq, + frame, + "resize", + &[width, height], + ) + .await?; + Ok(SequenceReturn::Return) + } + }); + Ok(CallbackReturn::Sequence(seq)) + }) +} + +pub(super) fn frame_resize<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let callback_return = { + let frame_tab: Table = Table::from_value( + ctx, + stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing object!"))?, + )?; + let width: Value = stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing width!"))?; + let height: Value = stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing height!"))?; + stack.consume(ctx)?; + + frame_tab.set(ctx, "width", width)?; + frame_tab.set(ctx, "height", height)?; + + let linked_mud: Value = frame_tab.get(ctx, "linked_mud")?; + + if linked_mud.is_nil() { + return Ok(piccolo::CallbackReturn::Return); + } + + let seq = async_sequence(&ctx, |locals, mut seq| { + let width = locals.stash(&ctx, width); + let height = locals.stash(&ctx, height); + let linked_mud = locals.stash(&ctx, linked_mud); + async move { + call_checking_metatable::( + &mut seq, + linked_mud, + "term_resized", + &[width, height], + ) + .await?; + + Ok(SequenceReturn::Return) + } + }); + + Ok(piccolo::CallbackReturn::Sequence(seq)) + }; + callback_return + }) +} + fn list_timers<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs index 241483b..f5180e2 100644 --- a/src/lua_engine/muds.rs +++ b/src/lua_engine/muds.rs @@ -14,14 +14,15 @@ use crate::{ id_intern::{intern_id, unintern_id}, logging::{start_deleting_logs, start_downloading_logs, start_listing_logs}, match_table::{create_match_table, match_table_add, match_table_remove}, - telnet::{parse_telnet_buf, TelnetOutput}, + telnet::{parse_telnet_buf, TelnetOutput, IAC}, websocket::{connect_websocket, send_message_to_mud, WebSocketId}, FrameId, GlobalLayoutCell, GlobalMemoCell, }; use self::telopt::{ - handle_incoming_do, handle_incoming_dont, handle_incoming_will, handle_incoming_wont, - MudWithMemo, Telopt, + get_option_state, handle_incoming_do, handle_incoming_dont, handle_incoming_will, + handle_incoming_wont, send_subnegotiation_if_allowed, set_option_supported, MudWithMemo, + OptionState, Side, Telopt, NAWS_TELOPT, }; use super::{call_checking_metatable, list_match_tab, try_unwrap_frame, LuaState}; @@ -87,8 +88,13 @@ pub(super) fn mudclose<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> let seq = piccolo::async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); async move { - call_checking_metatable::(&mut seq, conntab.clone(), "closed", &[]) - .await?; + call_checking_metatable::( + &mut seq, + conntab.clone(), + "closed", + &[], + ) + .await?; Ok(piccolo::SequenceReturn::Return) } }); @@ -169,12 +175,17 @@ pub(super) fn connect_mud<'gc>( let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mut trusted: bool = false; + let mut naws: bool = true; let name: String = loop { let v: String = stack.from_front(ctx)?; if v == "-trust" { trusted = true; continue; } + if v == "-no_naws" { + naws = false; + continue; + } break v; }; let name: Value<'gc> = ctx.intern(name.as_bytes()).into(); @@ -225,11 +236,16 @@ pub(super) fn connect_mud<'gc>( } } } + + if naws { + set_option_supported(ctx, &conntab, &NAWS_TELOPT, &Side::Us); + } + // Call conntab:new... let seq = async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); async move { - call_checking_metatable::(&mut seq, conntab, "new", &[]).await?; + call_checking_metatable::(&mut seq, conntab, "new", &[]).await?; Ok(SequenceReturn::Return) } }); @@ -344,7 +360,7 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) - .collect(); async move { for (func_name, params) in fns { - call_checking_metatable::( + call_checking_metatable::( &mut seq, conntab.clone(), func_name, @@ -434,7 +450,7 @@ pub(super) fn mudoutput_line<'gc>( let triggers = locals.stash(&ctx, triggers); let default_frame = locals.stash(&ctx, default_frame); async move { - call_checking_metatable::( + call_checking_metatable::( &mut seq, triggers, "try_run_sub", @@ -442,7 +458,7 @@ pub(super) fn mudoutput_line<'gc>( ) .await?; for frameroute in frameroutes { - call_checking_metatable::( + call_checking_metatable::( &mut seq, frameroute, "route", @@ -463,6 +479,19 @@ pub(super) fn mudoutput_prompt<'gc>( ) -> Callback<'gc> { Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return)) } + +fn name_telopt(ctx: Context, optno: u8) -> anyhow::Result { + for (k, v) in ctx + .get_global::("info")? + .get::<_, Table>(ctx, "telopts")? + { + if u8::from_value(ctx, v)? == optno { + return Ok(String::from_value(ctx, k)?); + } + } + Ok(format!("{}", optno)) +} + pub(super) fn mudoutput_will<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, @@ -477,8 +506,26 @@ pub(super) fn mudoutput_will<'gc>( memo: global_memo.clone(), mud: socket.clone(), }; - handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)); - Ok(CallbackReturn::Return) + if handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)) { + let seq = async_sequence(&ctx, move |locals, mut seq| { + let mud = locals.stash(&ctx, mud); + async move { + let name = + seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?; + call_checking_metatable::( + &mut seq, + mud, + format!("remote_{}_enabled", name), + &[], + ) + .await?; + Ok(SequenceReturn::Return) + } + }); + Ok(CallbackReturn::Sequence(seq)) + } else { + Ok(CallbackReturn::Return) + } }) } pub(super) fn mudoutput_wont<'gc>( @@ -510,8 +557,26 @@ pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell) memo: global_memo.clone(), mud: socket.clone(), }; - handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)); - Ok(CallbackReturn::Return) + if handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)) { + let seq = async_sequence(&ctx, move |locals, mut seq| { + let mud = locals.stash(&ctx, mud); + async move { + let name = + seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?; + call_checking_metatable::( + &mut seq, + mud, + format!("local_{}_enabled", name), + &[], + ) + .await?; + Ok(SequenceReturn::Return) + } + }); + Ok(CallbackReturn::Sequence(seq)) + } else { + Ok(CallbackReturn::Return) + } }) } pub(super) fn mudoutput_dont<'gc>( @@ -617,8 +682,13 @@ pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> { let mud = locals.stash(&ctx, mud); async move { - call_checking_metatable::(&mut seq, frameroute, "new", &[curr_frame]) - .await?; + call_checking_metatable::( + &mut seq, + frameroute, + "new", + &[curr_frame], + ) + .await?; seq.call(&create_match_tab, 0).await?; seq.try_enter(|ctx, locals, _ex, mut stack| { @@ -734,6 +804,73 @@ pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> { }) } +pub(super) fn mud_term_resized<'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, width, height): (Table, u16, u16) = stack.consume(ctx)?; + let socket: Value = mud.get(ctx, "socket")?; + let socket: &WebSocketId = + UserData::from_value(ctx, socket)?.downcast::]>()?; + send_subnegotiation_if_allowed( + ctx, + &mud, + &NAWS_TELOPT, + &Side::Us, + &mut MudWithMemo { + memo: global_memo.clone(), + mud: socket.clone(), + }, + &[ + (width >> 8) as u8, + (width & 0xFF) as u8, + (height >> 8) as u8, + (height & 0xFF) as u8, + ], + ); + Ok(CallbackReturn::Return) + }) +} + +pub(super) fn local_naws_enabled<'gc>( + ctx: Context<'gc>, + global_state_memo: &GlobalMemoCell, +) -> Callback<'gc> { + let global_state_memo = global_state_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let mud: Table = stack.consume(ctx)?; + let frameroutes: Table = mud.get(ctx, "frameroutes")?; + let default_frame: FrameId = try_unwrap_frame( + ctx, + &frameroutes + .iter() + .map(|(_k, v)| Ok::(Table::from_value(ctx, v)?)) + .next() + .unwrap_or_else(|| Ok(ctx.get_global::
("frames")?.get(ctx, 1_i64)?))? + .get(ctx, "frame")?, + )?; + let default_frame: Table = ctx + .get_global::
("frames")? + .get(ctx, default_frame.0 as i64)?; + let width: Value = default_frame.get(ctx, "width")?; + let height: Value = default_frame.get(ctx, "height")?; + if width.is_nil() || height.is_nil() { + return Ok(CallbackReturn::Return); + } + stack.push_back(mud.into_value(ctx)); + stack.push_back(width); + stack.push_back(height); + let cb = mud_term_resized(ctx, &global_state_memo); + + Ok(CallbackReturn::Call { + function: Function::Callback(cb), + then: None, + }) + }) +} + fn name_mud<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> String { fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result { let socket: Value<'gc> = mud.get(ctx, "socket")?; diff --git a/src/lua_engine/muds/telopt.rs b/src/lua_engine/muds/telopt.rs index e878f25..0fd54d4 100644 --- a/src/lua_engine/muds/telopt.rs +++ b/src/lua_engine/muds/telopt.rs @@ -4,7 +4,7 @@ use wasm_bindgen::JsValue; use web_sys::console; use crate::{ - telnet::{DO, DONT, IAC, WILL, WONT}, + telnet::{DO, DONT, ENDSUB, IAC, STARTSUB, WILL, WONT}, websocket::{send_message_to_mud, WebSocketId}, GlobalMemoCell, }; @@ -182,6 +182,29 @@ pub fn get_option_state<'gc>( } } +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 = 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>, @@ -214,7 +237,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>( 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) { @@ -226,6 +249,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>( &OptionState::Yes, ); send_positive(mud, opt); + return true; } else { send_negative(mud, opt); } @@ -255,15 +279,19 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>( &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::WantYes => set_option_state( - ctx, - table, - &opt.option, - &opt.option_active_on, - &OptionState::Yes, - ), OptionState::WantYesOpposite => { set_option_state( ctx, @@ -275,6 +303,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>( send_negative(mud, opt); } } + false } fn handle_incoming_negative<'gc, T: SendOptNeg>( @@ -334,7 +363,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>( mud: &mut T, table: &Table<'gc>, option: &Telopt, -) { +) -> bool { handle_incoming_positive( ctx, mud, @@ -343,7 +372,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>( option: option.clone(), option_active_on: Side::Him, }, - ); + ) } pub fn handle_incoming_wont<'gc, T: SendOptNeg>( @@ -368,7 +397,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>( mud: &mut T, table: &Table<'gc>, option: &Telopt, -) { +) -> bool { handle_incoming_positive( ctx, mud, @@ -377,7 +406,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>( option: option.clone(), option_active_on: Side::Us, }, - ); + ) } pub fn handle_incoming_dont<'gc, T: SendOptNeg>( @@ -398,6 +427,7 @@ pub fn handle_incoming_dont<'gc, T: SendOptNeg>( } pub const GMCP_TELOPT: Telopt = Telopt(201); +pub const NAWS_TELOPT: Telopt = Telopt(31); fn negotiate_option_on<'gc, T: SendOptNeg>( ctx: Context<'gc>, @@ -487,6 +517,15 @@ pub fn negotiate_option<'gc, T: SendOptNeg>( } } +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}; diff --git a/src/telnet.rs b/src/telnet.rs index 66f0265..2f57d07 100644 --- a/src/telnet.rs +++ b/src/telnet.rs @@ -29,8 +29,8 @@ const ERASECHAR: u8 = 247; const ERASELINE: u8 = 248; const GOAHEAD: u8 = 249; const EOR: u8 = 239; -const STARTSUB: u8 = 250; -const ENDSUB: u8 = 240; +pub const STARTSUB: u8 = 250; +pub const ENDSUB: u8 = 240; pub const WILL: u8 = 251; pub const WONT: u8 = 252; pub const DO: u8 = 253; @@ -146,22 +146,22 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec, Option) { } } Some(&STARTSUB) => { - match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) { + match try_parse_subneg(&ptr[1..]) { None => { // Including the STARTSUB... textbuf.push(IAC); textbuf.extend_from_slice(ptr); return (textbuf, None); } - Some(end_idx) => { - textbuf.extend_from_slice(&ptr[end_idx + 2..]); - return ( - textbuf, - Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())), - ); + Some((subneg, new_ptr)) => { + textbuf.extend_from_slice(new_ptr); + return (textbuf, Some(TelnetOutput::Subnegotiation(subneg))); } } } + Some(&IAC) => { + textbuf.push(IAC); + } Some(c) => { // Completely unexpected command. Warn and ignore. console::log_1(&JsValue::from_str(&format!( @@ -177,6 +177,34 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec, Option) { } } +fn try_parse_subneg(mut ptr: &[u8]) -> Option<(Vec, &[u8])> { + let mut subneg: Vec = Vec::new(); + loop { + match ptr.first() { + None => return None, + Some(&IAC) => { + ptr = &ptr[1..]; + match ptr.first() { + None => return None, + Some(&ENDSUB) => { + ptr = &ptr[1..]; + return Some((subneg, ptr)); + } + Some(c) => { + // For c != IAC this is not expected. + subneg.push(*c); + ptr = &ptr[1..]; + } + } + } + Some(c) => { + subneg.push(*c); + ptr = &ptr[1..]; + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -284,5 +312,12 @@ mod tests { Some(TelnetOutput::Subnegotiation(b"blah".to_vec())) ) ); + assert_eq!( + parse_telnet_buf(b"Hello \xff\xfablah\xff\xff\xff\xf0World!"), // Partial subnegotiation + ( + b"Hello World!".to_vec(), + Some(TelnetOutput::Subnegotiation(b"blah\xff".to_vec())) + ) + ); } }