Skip to content

huhlig/termionix

Repository files navigation

Termionix - Ansi Telnet Library for Tokio

License Build

(API Docs)

Termionix is an Ansi Enabled Telnet Library for Tokio.

Features

  • RFC 854 Compliant Telnet Protocol - Full implementation of the Telnet protocol
  • ANSI Escape Sequence Handling - Parse and generate ANSI codes for terminal control
  • MUD Protocol Extensions - Support for GMCP, MSDP, MSSP, MCCP, NAWS, and more
  • Async-First Design - Built on Tokio for high-performance async I/O
  • Split Read/Write Architecture - Independent read and write operations prevent blocking
  • Configurable Flush Strategies - Optimize for latency or throughput
  • Connection Metadata - Type-safe storage for per-connection data
  • Observability - Integrated tracing and metrics support
  • Ergonomic API - Easy-to-use high-level abstractions

Quick Start

Add Termionix to your Cargo.toml:

[dependencies]
termionix-service = "0.1"
termionix-terminal = "0.1"
tokio = { version = "1", features = ["full"] }

Create a simple telnet server:

use std::sync::Arc;
use termionix_server::{
    ConnectionManager, TelnetConnection, TelnetHandler, 
    TelnetServer, TelnetServerConfig,
};
use termionix_terminal::{TerminalCommand, TerminalEvent};

struct MyHandler;

#[async_trait::async_trait]
impl TelnetHandler for MyHandler {
    async fn on_connect(&self, conn: &TelnetConnection) {
        conn.send("Welcome to my server!\r\n").await.ok();
    }

    async fn on_data(&self, conn: &TelnetConnection, data: &str) {
        // Echo back to client
        conn.send(&format!("You said: {}\r\n", data)).await.ok();
    }

    async fn on_event(&self, conn: &TelnetConnection, event: TerminalEvent) {
        match event {
            TerminalEvent::WindowSize { width, height } => {
                conn.send(&format!("Window: {}x{}\r\n", width, height)).await.ok();
            }
            _ => {}
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = TelnetServerConfig {
        address: "127.0.0.1:4000".parse()?,
        ..Default::default()
    };
    
    let manager = Arc::new(ConnectionManager::new());
    let handler = Arc::new(MyHandler);
    let server = TelnetServer::new(config, handler, manager);
    
    server.run().await?;
    Ok(())
}

Advanced Features

Connection Metadata

Store typed data per connection:

#[derive(Clone)]
struct PlayerData {
    name: String,
    level: u32,
}

// Store data
let player = PlayerData { name: "Alice".to_string(), level: 5 };
conn.set_data("player", player);

// Retrieve data
if let Some(player) = conn.get_data::<PlayerData>("player") {
    println!("Player: {} (Level {})", player.name, player.level);
}

// Check existence
if conn.has_data("player") {
    // ...
}

// Remove data
conn.remove_data("player");

Negotiation Status

Query telnet option negotiation state:

// Get window size (NAWS)
if let Some((width, height)) = conn.window_size().await {
    println!("Terminal size: {}x{}", width, height);
}

// Get terminal type
if let Some(term_type) = conn.terminal_type().await {
    println!("Terminal: {}", term_type);
}

// Check if option is enabled
use termionix_telnetcodec::TelnetOption;
if conn.is_option_enabled(TelnetOption::Echo).await {
    println!("Echo is enabled");
}

Broadcasting

Send messages to multiple connections:

// Broadcast to all connections
manager.broadcast("Server announcement\r\n").await;

// Broadcast except specific connections
manager.broadcast_except("Player joined\r\n", &[conn.id()]).await;

// Broadcast with custom filter
manager.broadcast_filtered("Room message\r\n", |conn| {
    // Only send to connections in the same room
    conn.get_data::<RoomData>("room")
        .map(|r| r.id == target_room_id)
        .unwrap_or(false)
}).await;

Tracing Integration

Enable structured logging:

use tracing_subscriber;

tracing_subscriber::fmt()
    .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
    .init();

Run with: RUST_LOG=debug cargo run

Metrics Integration

Termionix automatically tracks:

  • Connection counts (total, active)
  • Message throughput (sent, received)
  • Character counts
  • Operation latency
  • Error rates

Integrate with your metrics backend:

use metrics_exporter_prometheus::PrometheusBuilder;

PrometheusBuilder::new()
    .install()
    .expect("failed to install Prometheus recorder");

Split Connection Architecture

Termionix uses a split read/write architecture that solves the blocking issue where buffered writes would wait for read timeouts:

use termionix_server::{SplitConnection, FlushStrategy};

// Create connection with independent read/write workers
let conn = SplitConnection::from_stream(stream, codec_read, codec_write);

// Configure flush strategy
conn.set_flush_strategy(FlushStrategy::OnNewline).await;

// Send data - doesn't block on reads!
conn.send("Hello\n", false).await?;

// Receive data - doesn't block on writes!
if let Some(event) = conn.next().await? {
    println!("Received: {:?}", event);
}

Benefits:

  • ✅ Reads never block writes
  • ✅ Writes never block reads
  • ✅ Configurable flush strategies (Manual, Immediate, OnNewline, OnThreshold)
  • ✅ Independent background workers
  • ✅ Zero mutex contention

See examples/split_connection_demo.rs for a complete demonstration.

Examples

See the examples/ directory for complete examples:

  • simple_server.rs - Basic telnet echo server
  • echo_server.rs - Echo server with connection management
  • split_connection_demo.rs - NEW: Demonstrates split read/write architecture
  • advanced_features.rs - Demonstrates all advanced features
  • ansi_demo.rs - ANSI escape sequence handling

Run an example:

cargo run --example split_connection_demo

Then connect with a telnet client:

telnet localhost 4000

Project Structure

  • .github - GitHub Actions Workflows and Issue Templates
  • ansicodec - ANSI String Handling Library
  • telnetcodec - Telnet Framed Codec for Tokio
  • terminal - ANSI Enabled Telnet Terminal
  • service - NEW: Unified connection layer with split read/write architecture
  • server - High-Level Telnet Server Framework
  • client - High-Level Telnet Client Framework
  • compress - MCCP Compression Support
  • doc - Documentation, Specifications, and RFCs
  • examples - Usage Examples

Documentation

Roadmap Items

  • Fix the myriad of Doctests
  • Add Support for GMCP as described here
  • Add Support for Compression and MCCP2 as described here
  • Add support for MMCP as described here
  • Add support for MNES as described here
  • Add support for MTTS as described here
  • Add support for MSLP as described here
  • Add Support for MSP as described here

License

This project is licensed under Apache License, Version 2.0.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

About

Ansi Telnet Library for Tokio

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages