Sign in

    telnet

    Transport-independent TELNET protocol library for MoonBit.

    telnet
    protocol
    moonbit
    rfc854
    Download zip
    Author
    Version
    0.1.3
    License
    MIT
    Last updated
    4 months ago
    Downloads
    30

    #jtenner/telnet

    Transport-independent TELNET protocol core for MoonBit.

    This package parses and emits TELNET byte streams, models TELNET commands and option negotiation, and provides codecs for common subnegotiation payloads. It is intended to sit below your TCP/TLS/runtime code and above application policy.

    #Status

    The core parser, encoder, option mapper, option-payload codecs, negotiation state APIs, and transport-independent Session composition layer are implemented and covered by a broad behavioral test corpus. Transport I/O, TLS handoff, credential handling, and terminal rendering remain application responsibilities.

    #Features

    • Incremental TELNET parser for arbitrary byte chunks.
    • TELNET encoder for data, commands, negotiation triplets, and subnegotiations.
    • Canonical IAC escaping for data and subnegotiation payloads.
    • WILL/WON'T/DO/DON'T negotiation state with explicit transition actions.
    • Transport-independent Session orchestration for parser, encoder, negotiator, policy, and option codecs.
    • Policy-driven option support with automatic replies to peer requests and opaque startup negotiation for built-in supported options such as remote NAWS.
    • RFC/IANA command and option mapping helpers.
    • Option-payload codecs for:
      • BINARY policy boundary
      • ECHO and SUPPRESS-GO-AHEAD negotiation
      • TERMINAL-TYPE
      • NAWS
      • LINEMODE
      • NEW-ENVIRON
      • CHARSET
      • START_TLS as a transport-security boundary codec
    • Lossless raw preservation for unknown/private option payloads.
    • Deterministic fuzz/property tests, target-parity checks, and native scanner stress coverage.

    #Non-goals

    • No TCP or async runtime dependency in the core package.
    • No built-in remote shell server.
    • No automatic credential handling.
    • No terminal emulator.
    • No bundled TLS implementation; START_TLS FOLLOWS is surfaced as a session boundary event for an adapter to upgrade the transport.

    #Get started with Session

    Use Session when you want the library to manage TELNET protocol state while your application owns the transport. A Session value contains the parser, encoder, RFC 1143 negotiator, option policy, START_TLS state, and tracked option metadata such as NAWS window size. Treat it as an immutable state value: every receive or send helper returns the next Session that you keep for the next I/O turn.

    1. Build a SessionConfig or start from Session::default().
    2. Allocate an output buffer large enough for generated TELNET replies.
    3. Call session.receive(input, out) for each inbound transport chunk.
    4. Write out[0:result.bytes_written] to your socket/TLS stream.
    5. Save result.session and handle result.events.

    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.
    }
    }

    SessionPolicy declares which options this endpoint supports. Accepted peer requests are answered automatically. The default server policy also starts remote NAWS negotiation on the first receive call; after the peer enables NAWS and sends a valid NAWS payload, read the latest size with session.get_window_size(). If local ECHO is negotiated, received data is escaped and echoed into the output buffer for you. Use send_data for outbound application bytes, request_option for explicit WILL/DO state changes, and send_payload for subnegotiation payloads. START_TLS FOLLOWS is surfaced as a TransportUpgradeRequired event; perform the actual TLS upgrade outside the session and then call session.mark_tls_active().

    #Quick examples

    #Parse a stream

    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)

    For streaming input, keep the returned parser and feed the next chunk:

    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)

    #Encode TELNET bytes

    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]

    Canonical data encoding doubles IAC bytes:

    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]

    #Decode subnegotiation payloads

    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
    }
    _ => ()
    }

    #Drive option negotiation

    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.

    #Use a Session

    Session composes parsing, negotiation, encoding, and option codecs without owning a socket. Keep the returned session between reads, write any generated bytes from the output buffer to your transport, and handle emitted events.

    ///|
    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.

    For server-side ECHO, accept local ECHO in SessionPolicy. Once Local ECHO is negotiated, incoming data is escaped and echoed into the output buffer. For NAWS, accept remote NAWS in policy; the session negotiates it opaquely on first receive, and Session::get_window_size() is updated after remote NAWS is enabled and a valid NAWS payload is received.

    #Main API areas

    • Parser: incremental byte-stream parser with feed, feed_span, finish, checkpoints, restore/reset, and configurable CR/coalescing/strictness policy.
    • Encoder: canonical and raw TELNET byte encoders with capacity reporting.
    • Negotiator: local/remote option-half state and explicit transition actions.
    • Session: streamable protocol-state composition with policy-driven option support, generated output bytes, server-side ECHO, START_TLS boundary events, and NAWS window-size tracking.
    • OptionPayload: decode/encode helpers for supported subnegotiation payloads.
    • ByteSpan: public byte-slice model used by parser events and encoders.
    • Mapping helpers: Command, NegotiationVerb, OptionCode, and KnownOption.

    #Policy boundaries

    The parser reports protocol events; Session adds TELNET state and policy but still does not own transport or application security decisions.

    • CrPolicy controls NVT CR/LF/CR-NUL handling at parser construction time; Session internally preserves parser data bytes so BINARY-aware semantics can be applied at the session layer.
    • SessionPolicy is the source of option support. Accepted incoming option requests are answered automatically; built-in proactive support such as remote NAWS is negotiated opaquely on the first Session::receive call.
    • BINARY negotiation is visible through negotiation state; it does not silently mutate parser configuration.
    • START_TLS FOLLOWS decodes as a payload and is surfaced as TransportUpgradeRequired; plaintext blocking and transport upgrade enforcement remain adapter responsibilities.
    • Unknown or unsupported option payloads can be preserved as OptionPayload::Raw.

    #Supported options

    CodeNameNotes
    0BINARYNegotiation/policy boundary; core parser exposes bytes.
    1ECHONegotiation support.
    3SUPPRESS-GO-AHEADNegotiation support.
    24TERMINAL-TYPESEND/IS payload codec.
    25END-OF-RECORDCommand/option mapping support.
    31NAWSWidth/height payload codec.
    34LINEMODEMode, SLC, and forward-mask payload codec.
    39NEW-ENVIRONIS/SEND/INFO variable payload codec.
    42CHARSETRequest/accept/reject and TTABLE payload codec.
    46START_TLSPayload codec; transport security is external.

    The full option source of truth is docs/wiki/04-options-catalog.md. IANA registry characterization is tracked in the executable tests and docs/wiki/09-verification-corpus.md.

    #Testing

    Normal validation:

    moon info moon fmt moon test

    Target-parity validation:

    moon test --target js moon test --target native

    Deterministic fuzz profile:

    moon run cmd/fuzz -- ci moon run --target wasm cmd/fuzz -- ci moon run --target wasm-gc cmd/fuzz -- ci

    Additional guards:

    bash tools/check-test-tautologies.sh git diff --check

    At the time this README was updated, moon coverage analyze reported no uncovered lines in the core api.mbt; remaining uncovered lines were in command harnesses under cmd/.

    #Benchmarks

    A runnable benchmark package lives in cmd/bench. It supports JavaScript timing via performance.now(), native timing via a small monotonic-clock C stub, and WebAssembly timing through a tiny JavaScript host runner:

    moon 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.wasm

    The runner reports elapsed milliseconds, operations/second, approximate MB/s for byte-oriented workloads, and a checksum for each benchmark.

    Before publishing performance numbers, collect repeated runs and average every benchmark row:

    node tools/collect-benchmarks.mjs --runs 5 \ --markdown _build/bench/report.md \ --out _build/bench/report.json

    Use --targets js-node,js-bun,native,wasm-node,wasm-bun,wasm-gc-bun to select a subset, --no-build to reuse existing artifacts, and --strict to fail instead of skipping unavailable runtimes such as Bun.

    Local benchmark baselines should be refreshed when performance-sensitive parser, encoder, or negotiator code changes. This table is intentionally compact; keep full command output in release notes or PR logs when a detailed comparison is needed.

    DateMachine / runtimeTargetRepresentative local results
    2026-05-25AMD Ryzen 7 8845HS, Node v20.19.2jsparser_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-25AMD Ryzen 7 8845HS, Bun 1.3.13jsparser_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-25AMD Ryzen 7 8845HS, Moon nativenativeparser_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-25AMD Ryzen 7 8845HS, Node v20.19.2wasmparser_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-25AMD Ryzen 7 8845HS, Bun 1.3.13wasmparser_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-25AMD Ryzen 7 8845HS, Bun 1.3.13wasm-gcparser_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.

    Note: this local Node v20.19.2 runtime could run the wasm build but could not instantiate the wasm-gc build; the wasm-gc row above was measured with Bun 1.3.13.

    #Documentation

    Start with docs/wiki/README.md. Useful pages include:

    Protocol claims should be backed by RFC Editor, IETF Datatracker, or IANA registry links in the wiki.

    #License

    MIT. See LICENSE.

    BufferOwnership

    pub(all) enum BufferOwnership {
    BorrowedInput
    Copied
    Scratch
    } derive(Eq,
    Debug
    )

    BufferSlice

    pub(all) struct BufferSlice {
    span : ByteSpan
    ownership : BufferOwnership
    } derive(Eq,
    Debug
    )

    ByteSpan

    pub(all) struct ByteSpan {
    bytes : Bytes
    start : Int
    length : Int
    } derive(Eq,
    Debug
    )

    ByteSpan::is_empty

    fn ByteSpan::is_empty(self : ByteSpan) -> Bool

    ByteSpan::new

    fn ByteSpan::new(bytes : Bytes, start : Int, length : Int) -> ByteSpan

    ByteSpan::to_bytes

    fn ByteSpan::to_bytes(self : ByteSpan) -> Bytes

    CharsetMessage

    pub(all) enum CharsetMessage {
    Request(Array[String])
    Accepted(String)
    Rejected
    TTableIs(String)
    TTableRejected
    TTableAck
    TTableNak
    } derive(Eq,
    Debug
    )

    CharsetMessage::decode

    fn CharsetMessage::decode(payload : ByteSpan) -> Result[CharsetMessage, TelnetError]

    CharsetMessage::encode

    fn CharsetMessage::encode(message : CharsetMessage, output : Bytes) -> Result[EncodeResult, EncodeError]

    Command

    pub(all) enum Command {
    SE
    NOP
    DM
    BRK
    IP
    AO
    AYT
    EC
    EL
    GA
    SB
    WILL
    WONT
    DO
    DONT
    IAC
    } derive(Eq,
    Debug
    )

    Command::from_byte

    fn Command::from_byte(byte : Byte) -> Command?

    Command::to_byte

    fn Command::to_byte(self : Command) -> Byte

    CommandEvent

    pub(all) struct CommandEvent {
    command : Command
    } derive(Eq,
    Debug
    )

    CrPolicy

    pub(all) enum CrPolicy {
    Preserve
    ValidateNvt
    NormalizeToLf
    } derive(Eq,
    Debug
    )

    DataCoalescing

    pub(all) enum DataCoalescing {
    EmitImmediately
    CoalesceUntilCommand
    CoalesceUntilCapacity
    } derive(Eq,
    Debug
    )

    DataEvent

    pub(all) struct DataEvent {
    span : ByteSpan
    } derive(Eq,
    Debug
    )

    EncodeError

    pub(all) struct EncodeError {
    kind : EncodeErrorKind
    offset : Int
    required_capacity : Int
    } derive(Eq,
    Debug
    )

    EncodeErrorKind

    pub(all) enum EncodeErrorKind {
    CommandRequiresIacEscape
    InvalidSubnegotiationPayload
    OutputBufferTooSmall
    UnsupportedEncoding
    } derive(Eq,
    Debug
    )

    EncodeItem

    pub(all) enum EncodeItem {
    RawData(ByteSpan)
    EscapedData(ByteSpan)
    Command(Command)
    Negotiation(NegotiationEvent)
    Subnegotiation(SubnegotiationEvent)
    } derive(Eq,
    Debug
    )

    EncodeResult

    pub(all) struct EncodeResult {
    bytes_written : Int
    required_capacity : Int
    } derive(Eq,
    Debug
    )

    Encoder

    pub(all) struct Encoder {
    canonical : Bool
    } derive(Eq,
    Debug
    )

    Encoder::canonical

    fn Encoder::canonical() -> Encoder

    Encoder::encode_command

    fn Encoder::encode_command(_self : Encoder, command : Command, output : Bytes) -> Result[EncodeResult, EncodeError]

    Encoder::encode_data

    fn Encoder::encode_data(self : Encoder, data : ByteSpan, output : Bytes) -> Result[EncodeResult, EncodeError]

    Encoder::encode_data_assume_capacity

    fn Encoder::encode_data_assume_capacity(self : Encoder, data : ByteSpan, output : Bytes, required : Int) -> Result[EncodeResult, EncodeError]

    Encoder::encode_item

    fn Encoder::encode_item(self : Encoder, item : EncodeItem, output : Bytes) -> Result[EncodeResult, EncodeError]

    Encoder::encode_negotiation

    fn Encoder::encode_negotiation(_self : Encoder, event : NegotiationEvent, output : Bytes) -> Result[EncodeResult, EncodeError]

    Encoder::encode_subnegotiation

    fn Encoder::encode_subnegotiation(_self : Encoder, event : SubnegotiationEvent, output : Bytes) -> Result[EncodeResult, EncodeError]

    Encoder::encode_subnegotiation_assume_capacity

    fn Encoder::encode_subnegotiation_assume_capacity(_self : Encoder, event : SubnegotiationEvent, output : Bytes, required : Int) -> Result[EncodeResult, EncodeError]

    Encoder::new

    fn Encoder::new() -> Encoder

    Encoder::required_capacity

    fn Encoder::required_capacity(_self : Encoder, item : EncodeItem) -> Int

    EnvironmentMessage

    pub(all) enum EnvironmentMessage {
    Is(Array[EnvironmentVariable])
    Send(Array[String])
    Info(Array[EnvironmentVariable])
    } derive(Eq,
    Debug
    )

    EnvironmentMessage::decode

    fn EnvironmentMessage::decode(payload : ByteSpan) -> Result[EnvironmentMessage, TelnetError]

    EnvironmentMessage::encode

    fn EnvironmentMessage::encode(message : EnvironmentMessage, output : Bytes) -> Result[EncodeResult, EncodeError]

    EnvironmentVariable

    pub(all) struct EnvironmentVariable {
    kind : EnvironmentVariableKind
    name : String
    value : String?
    } derive(Eq,
    Debug
    )

    EnvironmentVariableKind

    pub(all) enum EnvironmentVariableKind {
    Var
    UserVar
    } derive(Eq,
    Debug
    )

    Event

    pub(all) enum Event {
    Data(DataEvent)
    Command(CommandEvent)
    Negotiation(NegotiationEvent)
    Subnegotiation(SubnegotiationEvent)
    Error(TelnetError)
    } derive(Eq,
    Debug
    )

    HalfState

    pub(all) enum HalfState {
    No
    Yes
    WantNo(QueueBit)
    WantYes(QueueBit)
    } derive(Eq,
    Debug
    )

    KnownOption

    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
    )

    KnownOption::from_code

    fn KnownOption::from_code(code : OptionCode) -> KnownOption

    KnownOption::to_code

    fn KnownOption::to_code(self : KnownOption) -> OptionCode

    LinemodeForwardMask

    pub(all) enum LinemodeForwardMask {
    Default
    None
    Bytes(ByteSpan)
    } derive(Eq,
    Debug
    )

    LinemodeMessage

    pub(all) enum LinemodeMessage {
    Mode(LinemodeMode)
    ForwardMask(LinemodeForwardMask)
    Slc(ByteSpan)
    } derive(Eq,
    Debug
    )

    LinemodeMessage::decode

    fn LinemodeMessage::decode(payload : ByteSpan) -> Result[LinemodeMessage, TelnetError]

    LinemodeMessage::encode

    fn LinemodeMessage::encode(message : LinemodeMessage, output : Bytes) -> Result[EncodeResult, EncodeError]

    LinemodeMode

    pub(all) struct LinemodeMode {
    flags : Array[LinemodeModeFlag]
    } derive(Eq,
    Debug
    )

    LinemodeModeFlag

    pub(all) enum LinemodeModeFlag {
    Edit
    TrapSig
    ModeAck
    SoftTab
    LitEcho
    } derive(Eq,
    Debug
    )

    NawsSize

    pub(all) struct NawsSize {
    columns : UInt16
    rows : UInt16
    } derive(Eq,
    Debug
    )

    NawsSize::decode

    fn NawsSize::decode(payload : ByteSpan) -> Result[NawsSize, TelnetError]

    NawsSize::encode

    fn NawsSize::encode(size : NawsSize, output : Bytes) -> Result[EncodeResult, EncodeError]

    NegotiationAction

    pub(all) enum NegotiationAction {
    Send(NegotiationEvent)
    MarkEnabled(OptionSide, OptionCode)
    MarkDisabled(OptionSide, OptionCode)
    NotifyApplication(OptionState)
    NoAction
    } derive(Eq,
    Debug
    )

    NegotiationEvent

    pub(all) struct NegotiationEvent {
    verb : NegotiationVerb
    option : OptionCode
    } derive(Eq,
    Debug
    )

    NegotiationTransition

    pub(all) struct NegotiationTransition {
    before : OptionState
    input : NegotiationEvent
    decision : PolicyDecision
    after : OptionState
    actions : Array[NegotiationAction]
    } derive(Eq,
    Debug
    )

    NegotiationVerb

    pub(all) enum NegotiationVerb {
    Will
    Wont
    Do
    Dont
    } derive(Eq,
    Debug
    )

    NegotiationVerb::from_command

    fn NegotiationVerb::from_command(command : Command) -> NegotiationVerb?

    NegotiationVerb::to_command

    fn NegotiationVerb::to_command(self : NegotiationVerb) -> Command

    Negotiator

    pub(all) struct Negotiator {
    states : Array[OptionState]
    } derive(Eq,
    Debug
    )

    Negotiator::apply

    fn Negotiator::apply(self : Negotiator, transition : NegotiationTransition) -> Negotiator

    Negotiator::new

    fn Negotiator::new() -> Negotiator

    Negotiator::receive

    fn Negotiator::receive(self : Negotiator, input : NegotiationEvent, decision : PolicyDecision) -> NegotiationTransition

    Negotiator::request

    fn Negotiator::request(self : Negotiator, option : OptionCode, side : OptionSide, enable : Bool) -> NegotiationTransition

    Negotiator::state_for

    fn Negotiator::state_for(self : Negotiator, option : OptionCode) -> OptionState

    Negotiator::with_states

    fn Negotiator::with_states(states : Array[OptionState]) -> Negotiator

    OptionCode

    pub(all) struct OptionCode {
    value : Byte
    } derive(Eq,
    Debug
    )

    OptionCode::new

    fn OptionCode::new(byte : Byte) -> OptionCode

    OptionCode::to_byte

    fn OptionCode::to_byte(self : OptionCode) -> Byte

    OptionHalf

    pub(all) struct OptionHalf {
    option : OptionCode
    side : OptionSide
    state : HalfState
    } derive(Eq,
    Debug
    )

    OptionPayload

    pub(all) enum OptionPayload {
    Empty
    Raw(OptionCode, ByteSpan)
    TerminalType(TerminalTypeMessage)
    Naws(NawsSize)
    Environment(EnvironmentMessage)
    Charset(CharsetMessage)
    Linemode(LinemodeMessage)
    StartTls(StartTlsMessage)
    } derive(Eq,
    Debug
    )

    OptionPayload::decode

    fn OptionPayload::decode(option : OptionCode, payload : ByteSpan) -> Result[OptionPayload, TelnetError]

    OptionPayload::encode

    fn OptionPayload::encode(payload : OptionPayload, output : Bytes) -> Result[EncodeResult, EncodeError]

    OptionPayload::required_capacity

    fn OptionPayload::required_capacity(payload : OptionPayload) -> Int

    OptionPolicyRule

    pub(all) struct OptionPolicyRule {
    option : OptionCode
    side : OptionSide
    decision : PolicyDecision
    } derive(Eq,
    Debug
    )

    OptionSide

    pub(all) enum OptionSide {
    Local
    Remote
    } derive(Eq,
    Debug
    )

    OptionState

    pub(all) struct OptionState {
    option : OptionCode
    local_state : HalfState
    remote_state : HalfState
    } derive(Eq,
    Debug
    )

    Parser

    pub(all) struct Parser {
    config : ParserConfig
    checkpoint : ParserCheckpoint
    buffered_data : Bytes
    buffered_subnegotiation : Bytes
    pending_cr : Bool
    discarding_subnegotiation : Bool
    } derive(Eq,
    Debug
    )

    Parser::checkpoint

    fn Parser::checkpoint(self : Parser) -> ParserCheckpoint

    Parser::default

    fn Parser::default() -> Parser

    Parser::default_config

    fn Parser::default_config() -> ParserConfig

    Parser::feed

    fn Parser::feed(self : Parser, chunk : Bytes) -> ParserFeedResult

    Parser::feed_span

    fn Parser::feed_span(self : Parser, span : ByteSpan) -> ParserFeedResult

    Parser::feed_span_into

    fn Parser::feed_span_into(self : Parser, span : ByteSpan, events : Array[Event]) -> ParserFeedResult

    Parser::finish

    fn Parser::finish(self : Parser) -> ParserFinishResult

    Parser::new

    fn Parser::new(config : ParserConfig) -> Parser

    Parser::reset

    fn Parser::reset(self : Parser) -> Parser

    Parser::restore

    fn Parser::restore(config : ParserConfig, checkpoint : ParserCheckpoint) -> Parser

    Parser::with_config

    fn Parser::with_config(self : Parser, config : ParserConfig) -> Parser

    ParserCheckpoint

    pub(all) struct ParserCheckpoint {
    mode : ParserMode
    absolute_offset : Int
    buffered_data_length : Int
    buffered_subnegotiation_length : Int
    } derive(Eq,
    Debug
    )

    ParserConfig

    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
    )

    ParserFeedResult

    pub(all) struct ParserFeedResult {
    parser : Parser
    events : Array[Event]
    bytes_consumed : Int
    } derive(Eq,
    Debug
    )

    ParserFinishResult

    pub(all) struct ParserFinishResult {
    parser : Parser
    events : Array[Event]
    complete : Bool
    } derive(Eq,
    Debug
    )

    ParserMode

    pub(all) enum ParserMode {
    Normal
    AfterIac
    AfterNegotiationVerb(NegotiationVerb)
    AfterSubnegotiationStart
    InSubnegotiation(OptionCode)
    InSubnegotiationAfterIac(OptionCode)
    } derive(Eq,
    Debug
    )

    ParserState

    pub(all) struct ParserState {
    config : ParserConfig
    checkpoint : ParserCheckpoint
    } derive(Eq,
    Debug
    )

    PolicyDecision

    pub(all) enum PolicyDecision {
    Accept
    Reject
    Ignore
    } derive(Eq,
    Debug
    )

    PolicyQuestion

    pub(all) struct PolicyQuestion {
    side : OptionSide
    option : OptionCode
    requested_by_peer : Bool
    } derive(Eq,
    Debug
    )

    QueueBit

    pub(all) enum QueueBit {
    Empty
    Opposite
    } derive(Eq,
    Debug
    )

    Session

    pub(all) struct Session {
    parser : Parser
    encoder : Encoder
    negotiator : Negotiator
    config : SessionConfig
    tls_state : StartTlsState
    window_size : WindowSize?
    started : Bool
    } derive(Eq,
    Debug
    )

    Session::default

    fn Session::default() -> Session

    Session::default_config

    fn Session::default_config() -> SessionConfig

    Session::get_window_size

    fn Session::get_window_size(self : Session) -> WindowSize?

    Session::incoming_binary

    fn Session::incoming_binary(self : Session) -> Bool

    Session::local_option_enabled

    fn Session::local_option_enabled(self : Session, option : OptionCode) -> Bool

    Session::mark_tls_active

    fn Session::mark_tls_active(self : Session) -> Session

    Session::new

    fn Session::new(config : SessionConfig) -> Session

    Session::outgoing_binary

    fn Session::outgoing_binary(self : Session) -> Bool

    Session::receive

    fn Session::receive(self : Session, chunk : Bytes, output : Bytes) -> Result[SessionReceiveResult, SessionError]

    Session::remote_option_enabled

    fn Session::remote_option_enabled(self : Session, option : OptionCode) -> Bool

    Session::request_option

    fn Session::request_option(self : Session, option : OptionCode, side : OptionSide, enable : Bool, output : Bytes) -> Result[SessionSendResult, SessionError]

    Session::send_data

    fn Session::send_data(self : Session, data : ByteSpan, output : Bytes) -> Result[SessionSendResult, SessionError]

    Session::send_payload

    fn Session::send_payload(self : Session, payload : OptionPayload, output : Bytes) -> Result[SessionSendResult, SessionError]

    Session::state_for

    fn Session::state_for(self : Session, option : OptionCode) -> OptionState

    SessionConfig

    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
    )

    SessionError

    pub(all) struct SessionError {
    kind : SessionErrorKind
    offset : Int
    required_capacity : Int
    telnet_error : TelnetError?
    encode_error : EncodeError?
    } derive(Eq,
    Debug
    )

    SessionErrorKind

    pub(all) enum SessionErrorKind {
    OutputBufferTooSmall
    EncodeFailed
    MalformedOptionPayload
    PolicyRejected
    } derive(Eq,
    Debug
    )

    SessionEvent

    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
    )

    SessionPolicy

    pub(all) struct SessionPolicy {
    rules : Array[OptionPolicyRule]
    default_local : PolicyDecision
    default_remote : PolicyDecision
    } derive(Eq,
    Debug
    )

    SessionPolicy::decision_for

    fn SessionPolicy::decision_for(self : SessionPolicy, option : OptionCode, side : OptionSide) -> PolicyDecision

    SessionPolicy::default_client

    fn SessionPolicy::default_client() -> SessionPolicy

    SessionPolicy::default_server

    fn SessionPolicy::default_server() -> SessionPolicy

    SessionPolicy::permissive

    fn SessionPolicy::permissive() -> SessionPolicy

    SessionPolicy::reject_all

    fn SessionPolicy::reject_all() -> SessionPolicy

    SessionReceiveResult

    pub(all) struct SessionReceiveResult {
    session : Session
    events : Array[SessionEvent]
    bytes_written : Int
    bytes_consumed : Int
    } derive(Eq,
    Debug
    )

    SessionSendResult

    pub(all) struct SessionSendResult {
    session : Session
    events : Array[SessionEvent]
    bytes_written : Int
    } derive(Eq,
    Debug
    )

    SessionTextPolicy

    pub(all) enum SessionTextPolicy {
    Preserve
    ValidateNvt
    NormalizeToLf
    } derive(Eq,
    Debug
    )

    StartTlsMessage

    pub(all) enum StartTlsMessage {
    StartTls
    Follows
    } derive(Eq,
    Debug
    )

    StartTlsMessage::decode

    fn StartTlsMessage::decode(payload : ByteSpan) -> Result[StartTlsMessage, TelnetError]

    StartTlsMessage::encode

    fn StartTlsMessage::encode(message : StartTlsMessage, output : Bytes) -> Result[EncodeResult, EncodeError]

    StartTlsState

    pub(all) enum StartTlsState {
    Cleartext
    StartTlsRequested
    StartTlsFollows
    TlsActive
    TlsRejected
    } derive(Eq,
    Debug
    )

    SubnegotiationEvent

    pub(all) struct SubnegotiationEvent {
    option : OptionCode
    payload : ByteSpan
    } derive(Eq,
    Debug
    )

    TelnetError

    pub(all) struct TelnetError {
    kind : TelnetErrorKind
    offset : Int
    byte : Byte?
    option : OptionCode?
    } derive(Eq,
    Debug
    )

    TelnetErrorKind

    pub(all) enum TelnetErrorKind {
    IncompleteIac
    IncompleteNegotiation
    IncompleteSubnegotiation
    InvalidCommandByte
    InvalidOptionByte
    SubnegotiationTooLarge
    UnexpectedSubnegotiationEnd
    PolicyRejected
    UnsupportedOption
    } derive(Eq,
    Debug
    )

    TerminalTypeMessage

    pub(all) enum TerminalTypeMessage {
    Send
    Is(String)
    } derive(Eq,
    Debug
    )

    TerminalTypeMessage::decode

    TerminalTypeMessage::encode

    fn TerminalTypeMessage::encode(message : TerminalTypeMessage, output : Bytes) -> Result[EncodeResult, EncodeError]

    WindowSize

    pub(all) struct WindowSize {
    width : UInt16
    height : UInt16
    } derive(Eq,
    Debug
    )

    Source Files