Implement NAWS (negotiate about window size)

This commit is contained in:
Condorra 2024-11-09 22:53:30 +11:00
parent c0739262af
commit 78e36e4dcb
6 changed files with 375 additions and 52 deletions

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use gc_arena::Collect; use gc_arena::Collect;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::{Element, Node}; use web_sys::{console, Element, Node};
use yew::prelude::*; use yew::prelude::*;
use crate::{ use crate::{
@ -164,7 +164,15 @@ fn get_or_make_term_frame<'a>(
.ok() .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); term.onResize(&resize_closure);

View File

@ -1,5 +1,6 @@
use self::{frameroutes::*, frames::*, muds::*, storage::*}; use self::{frameroutes::*, frames::*, muds::*, storage::*};
use anyhow::Error; use anyhow::Error;
use muds::telopt::configure_telopt_table;
use piccolo::{ use piccolo::{
async_callback::{AsyncSequence, Locals}, async_callback::{AsyncSequence, Locals},
meta_ops::{self, MetaResult}, meta_ops::{self, MetaResult},
@ -127,6 +128,31 @@ impl LuaState {
self.interp.execute(&self.exec)?; self.interp.execute(&self.exec)?;
Ok(()) 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( pub fn install_lua_globals(
@ -209,6 +235,11 @@ pub fn install_lua_globals(
ctx.set_global("commands", cmd_table); ctx.set_global("commands", cmd_table);
let info_table = Table::new(&ctx); let info_table = Table::new(&ctx);
ctx.set_global("info", info_table); 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); let muds_table = Table::new(&ctx);
ctx.set_global("muds", muds_table); ctx.set_global("muds", muds_table);
let frames_table = Table::new(&ctx); 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, 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);
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 { macro_rules! register_class_nop {
($class_table: ident, $sym: ident) => { ($class_table: ident, $sym: ident) => {
@ -308,6 +341,7 @@ pub fn install_lua_globals(
frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?; 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, "new", new_frame);
register_class_function!(frame_class_table, "input", frame_input); 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); let frameroute_class_table = Table::new(&ctx);
classes_table.set(ctx, "frameroute", frameroute_class_table)?; 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, seq: &mut AsyncSequence,
obj: T, obj: T,
func_name: &'static str, func_name: FN,
arguments: &[StashedValue], arguments: &[StashedValue],
) -> Result<(), StashedError> ) -> Result<(), StashedError>
where where
for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>, for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>,
for<'gcb> FN: IntoValue<'gcb>,
{ {
let call = seq.try_enter(|ctx, locals, _execution, mut stack| { let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
let obj = locals.fetch(&obj); let obj = locals.fetch(&obj);
@ -400,11 +435,7 @@ where
ctx, ctx,
stack, stack,
locals, locals,
meta_ops::index( meta_ops::index(ctx, obj.into_value(ctx), func_name.into_value(ctx))?,
ctx,
obj.into_value(ctx),
ctx.intern_static(func_name.as_bytes()).into_value(ctx),
)?,
)) ))
})?; })?;
if let Some(call) = call { if let Some(call) = call {

View File

@ -106,7 +106,8 @@ pub fn list_match_tab<'gc>(
let frame = locals.stash(&ctx, intern_id(ctx, frame)); let frame = locals.stash(&ctx, intern_id(ctx, frame));
let aliases = locals.stash(&ctx, aliases); let aliases = locals.stash(&ctx, aliases);
async move { async move {
call_checking_metatable::<StashedUserData>(&mut seq, aliases, "lua_table", &[]).await?; call_checking_metatable::<StashedUserData, _>(&mut seq, aliases, "lua_table", &[])
.await?;
let echo_func = seq.try_enter(|ctx, locals, _ex, mut stack| { let echo_func = seq.try_enter(|ctx, locals, _ex, mut stack| {
let alias_tab: Table = stack.consume(ctx)?; let alias_tab: Table = stack.consume(ctx)?;
stack.push_back(locals.fetch(&frame).into_value(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_tab = locals.stash(&ctx, frame_tab);
let frame = locals.stash(&ctx, frame); let frame = locals.stash(&ctx, frame);
async move { async move {
call_checking_metatable::<StashedTable>(&mut seq, frame_tab, "new", &[frame]) call_checking_metatable::<StashedTable, _>(&mut seq, frame_tab, "new", &[frame])
.await?; .await?;
Ok(SequenceReturn::Return) 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 frame = locals.stash(&ctx, frame);
let line = locals.stash(&ctx, line.into_value(ctx)); let line = locals.stash(&ctx, line.into_value(ctx));
async move { async move {
call_checking_metatable::<StashedTable>(&mut seq, frame, "input", &[line]).await?; call_checking_metatable::<StashedTable, _>(&mut seq, frame, "input", &[line])
.await?;
Ok(SequenceReturn::Return) 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_tab = locals.stash(&ctx, frame_tab);
let frame = locals.stash(&ctx, frame); let frame = locals.stash(&ctx, frame);
async move { async move {
call_checking_metatable::<StashedUserData>( call_checking_metatable::<StashedUserData, _>(
&mut seq, &mut seq,
aliases.clone(), aliases.clone(),
"try_run_sub", "try_run_sub",
@ -426,7 +428,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
return Ok(SequenceReturn::Return); return Ok(SequenceReturn::Return);
} }
call_checking_metatable::<StashedValue>( call_checking_metatable::<StashedValue, _>(
&mut seq, &mut seq,
linked_mud, linked_mud,
"mudinput_line", "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::<StashedTable, _>(
&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::<StashedValue, _>(
&mut seq,
linked_mud,
"term_resized",
&[width, height],
)
.await?;
Ok(SequenceReturn::Return)
}
});
Ok(piccolo::CallbackReturn::Sequence(seq))
};
callback_return
})
}
fn list_timers<'gc>( fn list_timers<'gc>(
ctx: Context<'gc>, ctx: Context<'gc>,
global_memo: &GlobalMemoCell, global_memo: &GlobalMemoCell,

View File

@ -14,14 +14,15 @@ use crate::{
id_intern::{intern_id, unintern_id}, id_intern::{intern_id, unintern_id},
logging::{start_deleting_logs, start_downloading_logs, start_listing_logs}, logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
match_table::{create_match_table, match_table_add, match_table_remove}, 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}, websocket::{connect_websocket, send_message_to_mud, WebSocketId},
FrameId, GlobalLayoutCell, GlobalMemoCell, FrameId, GlobalLayoutCell, GlobalMemoCell,
}; };
use self::telopt::{ use self::telopt::{
handle_incoming_do, handle_incoming_dont, handle_incoming_will, handle_incoming_wont, get_option_state, handle_incoming_do, handle_incoming_dont, handle_incoming_will,
MudWithMemo, Telopt, 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}; use super::{call_checking_metatable, list_match_tab, try_unwrap_frame, LuaState};
@ -87,7 +88,12 @@ pub(super) fn mudclose<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) ->
let seq = piccolo::async_sequence(&ctx, |locals, mut seq| { let seq = piccolo::async_sequence(&ctx, |locals, mut seq| {
let conntab = locals.stash(&ctx, conntab); let conntab = locals.stash(&ctx, conntab);
async move { async move {
call_checking_metatable::<StashedTable>(&mut seq, conntab.clone(), "closed", &[]) call_checking_metatable::<StashedTable, _>(
&mut seq,
conntab.clone(),
"closed",
&[],
)
.await?; .await?;
Ok(piccolo::SequenceReturn::Return) Ok(piccolo::SequenceReturn::Return)
} }
@ -169,12 +175,17 @@ pub(super) fn connect_mud<'gc>(
let global_memo = global_memo.clone(); let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let mut trusted: bool = false; let mut trusted: bool = false;
let mut naws: bool = true;
let name: String = loop { let name: String = loop {
let v: String = stack.from_front(ctx)?; let v: String = stack.from_front(ctx)?;
if v == "-trust" { if v == "-trust" {
trusted = true; trusted = true;
continue; continue;
} }
if v == "-no_naws" {
naws = false;
continue;
}
break v; break v;
}; };
let name: Value<'gc> = ctx.intern(name.as_bytes()).into(); 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... // Call conntab:new...
let seq = async_sequence(&ctx, |locals, mut seq| { let seq = async_sequence(&ctx, |locals, mut seq| {
let conntab = locals.stash(&ctx, conntab); let conntab = locals.stash(&ctx, conntab);
async move { async move {
call_checking_metatable::<StashedTable>(&mut seq, conntab, "new", &[]).await?; call_checking_metatable::<StashedTable, _>(&mut seq, conntab, "new", &[]).await?;
Ok(SequenceReturn::Return) Ok(SequenceReturn::Return)
} }
}); });
@ -344,7 +360,7 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -
.collect(); .collect();
async move { async move {
for (func_name, params) in fns { for (func_name, params) in fns {
call_checking_metatable::<StashedTable>( call_checking_metatable::<StashedTable, _>(
&mut seq, &mut seq,
conntab.clone(), conntab.clone(),
func_name, func_name,
@ -434,7 +450,7 @@ pub(super) fn mudoutput_line<'gc>(
let triggers = locals.stash(&ctx, triggers); let triggers = locals.stash(&ctx, triggers);
let default_frame = locals.stash(&ctx, default_frame); let default_frame = locals.stash(&ctx, default_frame);
async move { async move {
call_checking_metatable::<StashedUserData>( call_checking_metatable::<StashedUserData, _>(
&mut seq, &mut seq,
triggers, triggers,
"try_run_sub", "try_run_sub",
@ -442,7 +458,7 @@ pub(super) fn mudoutput_line<'gc>(
) )
.await?; .await?;
for frameroute in frameroutes { for frameroute in frameroutes {
call_checking_metatable::<StashedTable>( call_checking_metatable::<StashedTable, _>(
&mut seq, &mut seq,
frameroute, frameroute,
"route", "route",
@ -463,6 +479,19 @@ pub(super) fn mudoutput_prompt<'gc>(
) -> Callback<'gc> { ) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return)) Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
} }
fn name_telopt(ctx: Context, optno: u8) -> anyhow::Result<String> {
for (k, v) in ctx
.get_global::<Table>("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>( pub(super) fn mudoutput_will<'gc>(
ctx: Context<'gc>, ctx: Context<'gc>,
global_memo: &GlobalMemoCell, global_memo: &GlobalMemoCell,
@ -477,8 +506,26 @@ pub(super) fn mudoutput_will<'gc>(
memo: global_memo.clone(), memo: global_memo.clone(),
mud: socket.clone(), mud: socket.clone(),
}; };
handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)); 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::<StashedTable, _>(
&mut seq,
mud,
format!("remote_{}_enabled", name),
&[],
)
.await?;
Ok(SequenceReturn::Return)
}
});
Ok(CallbackReturn::Sequence(seq))
} else {
Ok(CallbackReturn::Return) Ok(CallbackReturn::Return)
}
}) })
} }
pub(super) fn mudoutput_wont<'gc>( 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(), memo: global_memo.clone(),
mud: socket.clone(), mud: socket.clone(),
}; };
handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)); 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::<StashedTable, _>(
&mut seq,
mud,
format!("local_{}_enabled", name),
&[],
)
.await?;
Ok(SequenceReturn::Return)
}
});
Ok(CallbackReturn::Sequence(seq))
} else {
Ok(CallbackReturn::Return) Ok(CallbackReturn::Return)
}
}) })
} }
pub(super) fn mudoutput_dont<'gc>( pub(super) fn mudoutput_dont<'gc>(
@ -617,7 +682,12 @@ pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
let mud = locals.stash(&ctx, mud); let mud = locals.stash(&ctx, mud);
async move { async move {
call_checking_metatable::<StashedTable>(&mut seq, frameroute, "new", &[curr_frame]) call_checking_metatable::<StashedTable, _>(
&mut seq,
frameroute,
"new",
&[curr_frame],
)
.await?; .await?;
seq.call(&create_match_tab, 0).await?; seq.call(&create_match_tab, 0).await?;
@ -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::<Rootable![Gc<'_, WebSocketId>]>()?;
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, Error>(Table::from_value(ctx, v)?))
.next()
.unwrap_or_else(|| Ok(ctx.get_global::<Table>("frames")?.get(ctx, 1_i64)?))?
.get(ctx, "frame")?,
)?;
let default_frame: Table = ctx
.get_global::<Table>("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<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> String {
fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result<String> { fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result<String> {
let socket: Value<'gc> = mud.get(ctx, "socket")?; let socket: Value<'gc> = mud.get(ctx, "socket")?;

View File

@ -4,7 +4,7 @@ use wasm_bindgen::JsValue;
use web_sys::console; use web_sys::console;
use crate::{ use crate::{
telnet::{DO, DONT, IAC, WILL, WONT}, telnet::{DO, DONT, ENDSUB, IAC, STARTSUB, WILL, WONT},
websocket::{send_message_to_mud, WebSocketId}, websocket::{send_message_to_mud, WebSocketId},
GlobalMemoCell, 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<u8> = 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>( pub fn set_option_state<'gc>(
ctx: Context<'gc>, ctx: Context<'gc>,
input: &Table<'gc>, input: &Table<'gc>,
@ -214,7 +237,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
mud: &mut T, mud: &mut T,
table: &Table<'gc>, table: &Table<'gc>,
opt: &OptionWithActiveSide, opt: &OptionWithActiveSide,
) { ) -> bool {
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) { match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
OptionState::No => { OptionState::No => {
if get_option_supported(ctx, table, &opt.option, &opt.option_active_on) { 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, &OptionState::Yes,
); );
send_positive(mud, opt); send_positive(mud, opt);
return true;
} else { } else {
send_negative(mud, opt); send_negative(mud, opt);
} }
@ -255,15 +279,19 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
&opt.option, &opt.option,
&opt.option_active_on, &opt.option_active_on,
&OptionState::Yes, &OptionState::Yes,
) );
return true;
} }
OptionState::WantYes => set_option_state( OptionState::WantYes => {
set_option_state(
ctx, ctx,
table, table,
&opt.option, &opt.option,
&opt.option_active_on, &opt.option_active_on,
&OptionState::Yes, &OptionState::Yes,
), );
return true;
}
OptionState::WantYesOpposite => { OptionState::WantYesOpposite => {
set_option_state( set_option_state(
ctx, ctx,
@ -275,6 +303,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
send_negative(mud, opt); send_negative(mud, opt);
} }
} }
false
} }
fn handle_incoming_negative<'gc, T: SendOptNeg>( fn handle_incoming_negative<'gc, T: SendOptNeg>(
@ -334,7 +363,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
mud: &mut T, mud: &mut T,
table: &Table<'gc>, table: &Table<'gc>,
option: &Telopt, option: &Telopt,
) { ) -> bool {
handle_incoming_positive( handle_incoming_positive(
ctx, ctx,
mud, mud,
@ -343,7 +372,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
option: option.clone(), option: option.clone(),
option_active_on: Side::Him, option_active_on: Side::Him,
}, },
); )
} }
pub fn handle_incoming_wont<'gc, T: SendOptNeg>( pub fn handle_incoming_wont<'gc, T: SendOptNeg>(
@ -368,7 +397,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
mud: &mut T, mud: &mut T,
table: &Table<'gc>, table: &Table<'gc>,
option: &Telopt, option: &Telopt,
) { ) -> bool {
handle_incoming_positive( handle_incoming_positive(
ctx, ctx,
mud, mud,
@ -377,7 +406,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
option: option.clone(), option: option.clone(),
option_active_on: Side::Us, option_active_on: Side::Us,
}, },
); )
} }
pub fn handle_incoming_dont<'gc, T: SendOptNeg>( 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 GMCP_TELOPT: Telopt = Telopt(201);
pub const NAWS_TELOPT: Telopt = Telopt(31);
fn negotiate_option_on<'gc, T: SendOptNeg>( fn negotiate_option_on<'gc, T: SendOptNeg>(
ctx: Context<'gc>, 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)] #[cfg(test)]
mod tests { mod tests {
use piccolo::{Context, Lua, Table}; use piccolo::{Context, Lua, Table};

View File

@ -29,8 +29,8 @@ const ERASECHAR: u8 = 247;
const ERASELINE: u8 = 248; const ERASELINE: u8 = 248;
const GOAHEAD: u8 = 249; const GOAHEAD: u8 = 249;
const EOR: u8 = 239; const EOR: u8 = 239;
const STARTSUB: u8 = 250; pub const STARTSUB: u8 = 250;
const ENDSUB: u8 = 240; pub const ENDSUB: u8 = 240;
pub const WILL: u8 = 251; pub const WILL: u8 = 251;
pub const WONT: u8 = 252; pub const WONT: u8 = 252;
pub const DO: u8 = 253; pub const DO: u8 = 253;
@ -146,22 +146,22 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
} }
} }
Some(&STARTSUB) => { Some(&STARTSUB) => {
match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) { match try_parse_subneg(&ptr[1..]) {
None => { None => {
// Including the STARTSUB... // Including the STARTSUB...
textbuf.push(IAC); textbuf.push(IAC);
textbuf.extend_from_slice(ptr); textbuf.extend_from_slice(ptr);
return (textbuf, None); return (textbuf, None);
} }
Some(end_idx) => { Some((subneg, new_ptr)) => {
textbuf.extend_from_slice(&ptr[end_idx + 2..]); textbuf.extend_from_slice(new_ptr);
return ( return (textbuf, Some(TelnetOutput::Subnegotiation(subneg)));
textbuf,
Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())),
);
} }
} }
} }
Some(&IAC) => {
textbuf.push(IAC);
}
Some(c) => { Some(c) => {
// Completely unexpected command. Warn and ignore. // Completely unexpected command. Warn and ignore.
console::log_1(&JsValue::from_str(&format!( console::log_1(&JsValue::from_str(&format!(
@ -177,6 +177,34 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
} }
} }
fn try_parse_subneg(mut ptr: &[u8]) -> Option<(Vec<u8>, &[u8])> {
let mut subneg: Vec<u8> = 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -284,5 +312,12 @@ mod tests {
Some(TelnetOutput::Subnegotiation(b"blah".to_vec())) 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()))
)
);
} }
} }