Transport-independent TELNET protocol library for MoonBit.
let mut session = @telnet.Session::default()
let out = Bytes::new(8192)
let input = Bytes::from_array([
255.to_byte(), 251.to_byte(), 1.to_byte(), // IAC WILL ECHO
72.to_byte(), 105.to_byte(),
])
match session.receive(input, out) {
Ok(result) => {
session = result.session
// Write the first result.bytes_written bytes of out to your transport.
// Then inspect result.events for Data, negotiation state, decoded payloads,
// START_TLS boundaries, and option enable/disable notifications.
}
Err(error) => {
// Grow the output buffer if error.kind is OutputBufferTooSmall, or report
// malformed/policy errors according to your adapter's rules.
}
}let parser = @telnet.Parser::default()
let result = parser.feed(Bytes::from_array([
72.to_byte(), 105.to_byte(), 255.to_byte(), 246.to_byte(),
]))
// Events: Data("Hi"), Command(AYT)let first = @telnet.Parser::default().feed(Bytes::from_array([
255.to_byte(), 251.to_byte(),
]))
let second = first.parser.feed(Bytes::from_array([1.to_byte()]))
// Event: Negotiation(WILL, ECHO)let out = Bytes::new(3)
let event = @telnet.NegotiationEvent::{
verb: @telnet.NegotiationVerb::Will,
option: @telnet.OptionCode::new(1.to_byte()), // ECHO
}
let item = @telnet.EncodeItem::Negotiation(event)
ignore(@telnet.Encoder::canonical().encode_item(item, out))
// out == IAC WILL ECHO: [255, 251, 1]let data = @telnet.ByteSpan::new(
Bytes::from_array([65.to_byte(), 255.to_byte(), 66.to_byte()]),
0,
3,
)
let out = Bytes::new(4)
ignore(@telnet.Encoder::canonical().encode_item(
@telnet.EncodeItem::EscapedData(data),
out,
))
// out == [65, 255, 255, 66]let payload = @telnet.ByteSpan::new(
Bytes::from_array([0.to_byte(), 80.to_byte(), 0.to_byte(), 24.to_byte()]),
0,
4,
)
match @telnet.OptionPayload::decode(@telnet.OptionCode::new(31.to_byte()), payload) {
Ok(@telnet.OptionPayload::Naws(size)) => {
// size.columns == 80, size.rows == 24
}
_ => ()
}let negotiator = @telnet.Negotiator::new()
let incoming = @telnet.NegotiationEvent::{
verb: @telnet.NegotiationVerb::Will,
option: @telnet.OptionCode::new(1.to_byte()), // ECHO
}
let transition = negotiator.receive(incoming, @telnet.PolicyDecision::Accept)
let updated = negotiator.apply(transition)
// Encode any NegotiationAction::Send replies and write them on your transport.///|
let session = @telnet.Session::new(@telnet.SessionConfig::{
parser_config: @telnet.Parser::default_config(),
policy: @telnet.SessionPolicy::default_server(),
incoming_text_policy: @telnet.SessionTextPolicy::Preserve,
outgoing_text_policy: @telnet.SessionTextPolicy::Preserve,
decode_known_options: true,
emit_raw_subnegotiation: true,
require_enabled_for_subnegotiation: false,
reject_malformed_known_payloads: false,
max_outbound_bytes: 8192,
})
///|
let out = Bytes::new(64)
///|
let result = session.receive(Bytes::new(0), out)
// With default_server policy, the first receive opaquely starts remote NAWS
// negotiation by writing IAC DO NAWS into out.| Code | Name | Notes |
|---|---|---|
| 0 | BINARY | Negotiation/policy boundary; core parser exposes bytes. |
| 1 | ECHO | Negotiation support. |
| 3 | SUPPRESS-GO-AHEAD | Negotiation support. |
| 24 | TERMINAL-TYPE | SEND/IS payload codec. |
| 25 | END-OF-RECORD | Command/option mapping support. |
| 31 | NAWS | Width/height payload codec. |
| 34 | LINEMODE | Mode, SLC, and forward-mask payload codec. |
| 39 | NEW-ENVIRON | IS/SEND/INFO variable payload codec. |
| 42 | CHARSET | Request/accept/reject and TTABLE payload codec. |
| 46 | START_TLS | Payload codec; transport security is external. |
moon info
moon fmt
moon testmoon test --target js
moon test --target nativemoon run cmd/fuzz -- ci
moon run --target wasm cmd/fuzz -- ci
moon run --target wasm-gc cmd/fuzz -- cibash tools/check-test-tautologies.sh
git diff --checkmoon run --target js cmd/bench
moon run --target native cmd/bench
moon build --target wasm cmd/bench
node tools/run-wasm-bench.mjs _build/wasm/debug/build/cmd/bench/bench.wasm
moon build --target wasm-gc cmd/bench
bun tools/run-wasm-bench.mjs _build/wasm-gc/debug/build/cmd/bench/bench.wasmnode tools/collect-benchmarks.mjs --runs 5 \
--markdown _build/bench/report.md \
--out _build/bench/report.json| Date | Machine / runtime | Target | Representative local results |
|---|---|---|---|
| 2026-05-25 | AMD Ryzen 7 8845HS, Node v20.19.2 | js | parser_plain_1m_8192_chunks: 611 MB/s; parser_iac_escaped_sparse: 171 MB/s; encoder_raw_1m_assume_capacity: 327 MB/s; e2e_interactive_shell: 6,170 ops/s. |
| 2026-05-25 | AMD Ryzen 7 8845HS, Bun 1.3.13 | js | parser_plain_1m_8192_chunks: 1,645 MB/s; parser_iac_escaped_sparse: 428 MB/s; encoder_raw_1m_assume_capacity: 1,134 MB/s; e2e_interactive_shell: 9,083 ops/s. |
| 2026-05-25 | AMD Ryzen 7 8845HS, Moon native | native | parser_plain_1m_8192_chunks: 21,531 MB/s; parser_iac_escaped_sparse: 178 MB/s; encoder_raw_1m_assume_capacity: 441 MB/s; e2e_interactive_shell: 2,596 ops/s. |
| 2026-05-25 | AMD Ryzen 7 8845HS, Node v20.19.2 | wasm | parser_plain_1m_8192_chunks: 864 MB/s; parser_iac_escaped_sparse: 97 MB/s; encoder_raw_1m_assume_capacity: 713 MB/s; e2e_interactive_shell: 3,061 ops/s. |
| 2026-05-25 | AMD Ryzen 7 8845HS, Bun 1.3.13 | wasm | parser_plain_1m_8192_chunks: 1,658 MB/s; parser_iac_escaped_sparse: 193 MB/s; encoder_raw_1m_assume_capacity: 774 MB/s; e2e_interactive_shell: 4,280 ops/s. |
| 2026-05-25 | AMD Ryzen 7 8845HS, Bun 1.3.13 | wasm-gc | parser_plain_1m_8192_chunks: 2,030 MB/s; parser_iac_escaped_sparse: 675 MB/s; encoder_raw_1m_assume_capacity: 2,035 MB/s; e2e_interactive_shell: 10,711 ops/s. |
fn CharsetMessage::encode(message : CharsetMessage, output : Bytes) -> Result[EncodeResult, EncodeError]pub(all) struct EncodeError {
kind : EncodeErrorKind
offset : Int
required_capacity : Int
} derive(Eq, Debug)pub(all) enum EncodeItem {
RawData(ByteSpan)
EscapedData(ByteSpan)
Command(Command)
Negotiation(NegotiationEvent)
Subnegotiation(SubnegotiationEvent)
} derive(Eq, Debug)fn Encoder::encode_command(_self : Encoder, command : Command, output : Bytes) -> Result[EncodeResult, EncodeError]fn Encoder::encode_data(self : Encoder, data : ByteSpan, output : Bytes) -> Result[EncodeResult, EncodeError]fn Encoder::encode_data_assume_capacity(self : Encoder, data : ByteSpan, output : Bytes, required : Int) -> Result[EncodeResult, EncodeError]fn Encoder::encode_item(self : Encoder, item : EncodeItem, output : Bytes) -> Result[EncodeResult, EncodeError]fn Encoder::encode_negotiation(_self : Encoder, event : NegotiationEvent, output : Bytes) -> Result[EncodeResult, EncodeError]fn Encoder::encode_subnegotiation(_self : Encoder, event : SubnegotiationEvent, output : Bytes) -> Result[EncodeResult, EncodeError]fn Encoder::encode_subnegotiation_assume_capacity(_self : Encoder, event : SubnegotiationEvent, output : Bytes, required : Int) -> Result[EncodeResult, EncodeError]pub(all) enum EnvironmentMessage {
Is(Array[EnvironmentVariable])
Send(Array[String])
Info(Array[EnvironmentVariable])
} derive(Eq, Debug)fn EnvironmentMessage::encode(message : EnvironmentMessage, output : Bytes) -> Result[EncodeResult, EncodeError]pub(all) struct EnvironmentVariable {
kind : EnvironmentVariableKind
name : String
value : String?
} derive(Eq, Debug)pub(all) enum Event {
Data(DataEvent)
Command(CommandEvent)
Negotiation(NegotiationEvent)
Subnegotiation(SubnegotiationEvent)
Error(TelnetError)
} derive(Eq, Debug)pub(all) enum KnownOption {
Binary
Echo
Reconnection
SuppressGoAhead
ApproxMessageSizeNegotiation
Status
TimingMark
RemoteControlledTransAndEcho
OutputLineWidth
OutputPageSize
OutputCarriageReturnDisposition
OutputHorizontalTabStops
OutputHorizontalTabDisposition
OutputFormfeedDisposition
OutputVerticalTabStops
OutputVerticalTabDisposition
OutputLinefeedDisposition
ExtendedAscii
Logout
ByteMacro
DataEntryTerminal
Supdup
SupdupOutput
SendLocation
TerminalType
EndOfRecord
TacacsUserIdentification
OutputMarking
TerminalLocationNumber
Telnet3270Regime
X3Pad
Naws
TerminalSpeed
RemoteFlowControl
Linemode
XDisplayLocation
Environment
Authentication
Encryption
NewEnvironment
Tn3270e
Xauth
Charset
TelnetRemoteSerialPort
ComPortControl
TelnetSuppressLocalEcho
TelnetStartTls
Kermit
SendUrl
ForwardX
ExtendedOptionsList
Unknown(OptionCode)
} derive(Eq, Debug)pub(all) enum LinemodeMessage {
Mode(LinemodeMode)
ForwardMask(LinemodeForwardMask)
Slc(ByteSpan)
} derive(Eq, Debug)fn LinemodeMessage::encode(message : LinemodeMessage, output : Bytes) -> Result[EncodeResult, EncodeError]pub(all) enum NegotiationAction {
Send(NegotiationEvent)
MarkEnabled(OptionSide, OptionCode)
MarkDisabled(OptionSide, OptionCode)
NotifyApplication(OptionState)
NoAction
} derive(Eq, Debug)pub(all) struct NegotiationTransition {
before : OptionState
input : NegotiationEvent
decision : PolicyDecision
after : OptionState
actions : Array[NegotiationAction]
} derive(Eq, Debug)fn Negotiator::receive(self : Negotiator, input : NegotiationEvent, decision : PolicyDecision) -> NegotiationTransitionfn Negotiator::request(self : Negotiator, option : OptionCode, side : OptionSide, enable : Bool) -> NegotiationTransitionpub(all) struct OptionHalf {
option : OptionCode
side : OptionSide
state : HalfState
} derive(Eq, Debug)pub(all) enum OptionPayload {
Empty
Raw(OptionCode, ByteSpan)
TerminalType(TerminalTypeMessage)
Naws(NawsSize)
Environment(EnvironmentMessage)
Charset(CharsetMessage)
Linemode(LinemodeMessage)
StartTls(StartTlsMessage)
} derive(Eq, Debug)fn OptionPayload::decode(option : OptionCode, payload : ByteSpan) -> Result[OptionPayload, TelnetError]fn OptionPayload::encode(payload : OptionPayload, output : Bytes) -> Result[EncodeResult, EncodeError]pub(all) struct OptionPolicyRule {
option : OptionCode
side : OptionSide
decision : PolicyDecision
} derive(Eq, Debug)pub(all) struct OptionState {
option : OptionCode
local_state : HalfState
remote_state : HalfState
} derive(Eq, Debug)pub(all) struct Parser {
config : ParserConfig
checkpoint : ParserCheckpoint
buffered_data : Bytes
buffered_subnegotiation : Bytes
pending_cr : Bool
discarding_subnegotiation : Bool
} derive(Eq, Debug)pub(all) struct ParserCheckpoint {
mode : ParserMode
absolute_offset : Int
buffered_data_length : Int
buffered_subnegotiation_length : Int
} derive(Eq, Debug)pub(all) struct ParserConfig {
max_data_chunk_bytes : Int
max_subnegotiation_bytes : Int
cr_policy : CrPolicy
data_coalescing : DataCoalescing
strict_commands : Bool
} derive(Eq, Debug)pub(all) enum ParserMode {
Normal
AfterIac
AfterNegotiationVerb(NegotiationVerb)
AfterSubnegotiationStart
InSubnegotiation(OptionCode)
InSubnegotiationAfterIac(OptionCode)
} derive(Eq, Debug)pub(all) struct ParserState {
config : ParserConfig
checkpoint : ParserCheckpoint
} derive(Eq, Debug)pub(all) struct PolicyQuestion {
side : OptionSide
option : OptionCode
requested_by_peer : Bool
} derive(Eq, Debug)pub(all) struct Session {
parser : Parser
encoder : Encoder
negotiator : Negotiator
config : SessionConfig
tls_state : StartTlsState
window_size : WindowSize?
started : Bool
} derive(Eq, Debug)fn Session::receive(self : Session, chunk : Bytes, output : Bytes) -> Result[SessionReceiveResult, SessionError]fn Session::request_option(self : Session, option : OptionCode, side : OptionSide, enable : Bool, output : Bytes) -> Result[SessionSendResult, SessionError]fn Session::send_data(self : Session, data : ByteSpan, output : Bytes) -> Result[SessionSendResult, SessionError]fn Session::send_payload(self : Session, payload : OptionPayload, output : Bytes) -> Result[SessionSendResult, SessionError]pub(all) struct SessionConfig {
parser_config : ParserConfig
policy : SessionPolicy
incoming_text_policy : SessionTextPolicy
outgoing_text_policy : SessionTextPolicy
decode_known_options : Bool
emit_raw_subnegotiation : Bool
require_enabled_for_subnegotiation : Bool
reject_malformed_known_payloads : Bool
max_outbound_bytes : Int
} derive(Eq, Debug)pub(all) struct SessionError {
kind : SessionErrorKind
offset : Int
required_capacity : Int
telnet_error : TelnetError?
encode_error : EncodeError?
} derive(Eq, Debug)pub(all) enum SessionEvent {
Data(ByteSpan)
Command(Command)
NegotiationReceived(NegotiationEvent)
NegotiationSent(NegotiationEvent)
OptionEnabled(OptionSide, OptionCode)
OptionDisabled(OptionSide, OptionCode)
OptionRejected(OptionSide, OptionCode)
Subnegotiation(SubnegotiationEvent)
OptionPayload(OptionPayload)
StartTlsRequested
StartTlsFollows
TransportUpgradeRequired
Error(TelnetError)
} derive(Eq, Debug)pub(all) struct SessionPolicy {
rules : Array[OptionPolicyRule]
default_local : PolicyDecision
default_remote : PolicyDecision
} derive(Eq, Debug)fn SessionPolicy::decision_for(self : SessionPolicy, option : OptionCode, side : OptionSide) -> PolicyDecisionpub(all) struct SessionReceiveResult {
session : Session
events : Array[SessionEvent]
bytes_written : Int
bytes_consumed : Int
} derive(Eq, Debug)pub(all) struct SessionSendResult {
session : Session
events : Array[SessionEvent]
bytes_written : Int
} derive(Eq, Debug)fn StartTlsMessage::encode(message : StartTlsMessage, output : Bytes) -> Result[EncodeResult, EncodeError]pub(all) struct TelnetError {
kind : TelnetErrorKind
offset : Int
byte : Byte?
option : OptionCode?
} derive(Eq, Debug)fn TerminalTypeMessage::encode(message : TerminalTypeMessage, output : Bytes) -> Result[EncodeResult, EncodeError]Install
Download zipTransport-independent TELNET protocol library for MoonBit.