tow 0.1.1

A tiny TCP over WebSockets tunnel.
use std::net::{SocketAddr, ToSocketAddrs};

use anyhow::Context;
use axum::{
    Router,
    extract::{
        State, WebSocketUpgrade,
        ws::{Message, WebSocket},
    },
    response::Response,
    routing,
};
use futures_util::{
    SinkExt, StreamExt,
    stream::{SplitSink, SplitStream},
};
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    net::{
        TcpListener, TcpStream,
        tcp::{OwnedReadHalf, OwnedWriteHalf},
    },
};

use crate::args::ServerArgs;

#[derive(Clone, Copy)]
struct TowState {
    pub address: SocketAddr,
}

pub async fn run(args: ServerArgs) -> anyhow::Result<()> {
    let listener = TcpListener::bind(args.from)
        .await
        .context("Could not bind to the specified address (`--from`).")?;

    let router = Router::new()
        .route("/", routing::any(gate))
        .with_state(TowState {
            address: args
                .to
                .to_socket_addrs()
                .context("Could not get socket address from `--to` argument.")?
                .next()
                .context("The provided `--to` argument did not resolve to any socket addresses.")?,
        });

    axum::serve(listener, router.into_make_service())
        .await
        .context("An error occurred while listening for incoming connections.")?;

    Ok(())
}

async fn gate(ws: WebSocketUpgrade, State(state): State<TowState>) -> Response {
    ws.on_upgrade(move |ws| handle(ws, state))
}

async fn handle(ws: WebSocket, state: TowState) {
    let Ok(connection) = TcpStream::connect(state.address).await else {
        return;
    };

    let (client_sender, client_receiver) = ws.split();
    let (server_receiver, server_sender) = connection.into_split();

    let outgoing_task = tokio::spawn(handle_outgoing(server_receiver, client_sender));
    let incoming_task = tokio::spawn(handle_incoming(server_sender, client_receiver));

    tokio::select! {
        _ = outgoing_task => {},
        _ = incoming_task => {},
    }
}

async fn handle_outgoing(
    mut server_receiver: OwnedReadHalf,
    mut client_sender: SplitSink<WebSocket, Message>,
) -> anyhow::Result<()> {
    loop {
        let mut buffer = Vec::new();
        server_receiver
            .read(&mut buffer)
            .await
            .context("Could not read outgoing data.")?;
        client_sender
            .send(Message::Binary(buffer.into()))
            .await
            .context("Could not send binary data to client.")?;
    }
}

async fn handle_incoming(
    mut server_sender: OwnedWriteHalf,
    mut client_receiver: SplitStream<WebSocket>,
) -> anyhow::Result<()> {
    loop {
        let message = client_receiver
            .next()
            .await
            .context("No more messages from client.")?
            .context("Could not read message from client.")?;

        match message {
            Message::Binary(bytes) => server_sender
                .write_all(&bytes)
                .await
                .context("Could not send client's bytes to server.")?,
            Message::Text(text) => server_sender
                .write_all(text.as_bytes())
                .await
                .context("Could not send client's text to server.")?,
            Message::Close(_) => {
                server_sender.forget();
                break;
            }
            _ => {}
        }
    }

    Ok(())
}