灵符lingfu

ling-net

↓ 0 downloads

Async networking — HTTP, WebSocket, QUIC, and peer-to-peer

Keywords net, async, http, quic, websocket, p2p
lingfu install ling-net
LicenseApache-2.0 OR MIT
AuthorsLing Lin, Sanny Lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (26)

src/lingtp/server.rs
use std::io;
use std::net::TcpListener;
use std::path::Path;
use std::sync::Arc;
use std::thread;

use ling_crypto::MlDsa87Keypair;

use super::connection::Connection;
use super::envelope::{LingtpRequest, LingtpResponse};
use super::handshake::server_handshake;
use super::util::to_hex;

pub type Handler = dyn Fn(&LingtpRequest) -> LingtpResponse + Send + Sync;

/// Load a persisted ML-DSA-87 identity keypair from `path` (a 32-byte seed
/// file), or generate a fresh one and persist it there if it doesn't exist
/// yet. Conventionally `.lingtp/host_mldsa87`.
pub fn load_or_generate_identity(path: impl AsRef<Path>) -> io::Result<MlDsa87Keypair> {
    let path = path.as_ref();
    if let Ok(bytes) = std::fs::read(path) {
        if bytes.len() == 32 {
            let mut seed = [0u8; 32];
            seed.copy_from_slice(&bytes);
            return Ok(MlDsa87Keypair::from_seed(seed));
        }
    }
    let kp = MlDsa87Keypair::generate();
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, &*kp.to_bytes())?;
    Ok(kp)
}

/// Serve lingtp:// on `host:port`, calling `handler` for every request on
/// every connection (thread-per-connection; lingtp is keep-alive, so many
/// requests may share one handshake). Blocks the calling thread forever.
pub fn serve(
    host: &str,
    port: u16,
    identity: MlDsa87Keypair,
    handler: impl Fn(&LingtpRequest) -> LingtpResponse + Send + Sync + 'static,
) -> io::Result<()> {
    let listener = TcpListener::bind((host, port))?;
    let identity_hex = to_hex(&identity.public_key());
    eprintln!(
        "lingtp://{host}:{port}/ listening — identity {}...",
        &identity_hex[..16.min(identity_hex.len())]
    );
    serve_on(listener, identity, handler)
}

/// Same as [`serve`], but on an already-bound listener — useful for binding
/// to an OS-assigned ephemeral port (`"127.0.0.1:0"`) in tests, or for
/// socket-activation setups where the listener is inherited from elsewhere.
pub fn serve_on(
    listener: TcpListener,
    identity: MlDsa87Keypair,
    handler: impl Fn(&LingtpRequest) -> LingtpResponse + Send + Sync + 'static,
) -> io::Result<()> {
    let identity = Arc::new(identity);
    let handler: Arc<Handler> = Arc::new(handler);

    for incoming in listener.incoming() {
        let stream = match incoming {
            Ok(s) => s,
            Err(_) => continue,
        };
        let identity = Arc::clone(&identity);
        let handler = Arc::clone(&handler);
        thread::spawn(move || {
            if let Ok(conn) = server_handshake(stream, &identity) {
                handle_connection(conn, handler);
            }
        });
    }
    Ok(())
}

fn handle_connection(mut conn: Connection, handler: Arc<Handler>) {
    loop {
        match conn.recv_request() {
            Ok(Some(req)) => {
                let resp = handler(&req);
                if conn.send_response(&resp).is_err() {
                    break;
                }
            }
            _ => break,
        }
    }
}

← Back to index