[refactor](trx-rs): inject runtime contexts for io paths

Phase 3: replace frontend/backend hot-path globals with explicit runtime/registration context wiring while keeping plugin compatibility adapters.

Co-authored-by: Codex <codex@openai.com>,
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-12 21:18:42 +01:00
parent 410fc89185
commit b7fb9adef7
12 changed files with 220 additions and 236 deletions
@@ -4,11 +4,12 @@
pub mod server;
pub fn register_frontend_on(context: &mut trx_frontend::FrontendRegistrationContext) {
use trx_frontend::FrontendSpawner;
context.register_frontend("http-json", server::HttpJsonFrontend::spawn_frontend);
}
pub fn register_frontend() {
use trx_frontend::FrontendSpawner;
trx_frontend::register_frontend("http-json", server::HttpJsonFrontend::spawn_frontend);
}
pub fn set_auth_tokens(tokens: Vec<String>) {
server::set_auth_tokens(tokens);
}
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: BSD-2-Clause
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream};
@@ -10,70 +11,24 @@ use tokio::sync::{mpsc, oneshot, watch};
use tokio::task::JoinHandle;
use tracing::{error, info};
use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
use trx_core::rig::request::RigRequest;
use trx_core::rig::state::RigState;
use trx_core::{ClientResponse};
use trx_frontend::FrontendSpawner;
use trx_frontend::{FrontendSpawner, FrontendRuntimeContext};
use trx_protocol::auth::{SimpleTokenValidator, TokenValidator};
use trx_protocol::codec::parse_envelope;
use trx_protocol::auth::TokenValidator;
use trx_protocol::mapping;
use trx_protocol::ClientResponse;
/// JSON-over-TCP frontend for control and status.
pub struct HttpJsonFrontend;
struct AuthConfig {
tokens: HashSet<String>,
}
fn auth_registry() -> &'static Mutex<AuthConfig> {
static REGISTRY: OnceLock<Mutex<AuthConfig>> = OnceLock::new();
REGISTRY.get_or_init(|| {
Mutex::new(AuthConfig {
tokens: HashSet::new(),
})
})
}
pub fn set_auth_tokens(tokens: Vec<String>) {
let mut reg = auth_registry()
.lock()
.expect("http-json auth mutex poisoned");
reg.tokens = tokens.into_iter().filter(|t| !t.is_empty()).collect();
}
/// Token validator that uses the global auth registry.
struct RegistryTokenValidator;
impl TokenValidator for RegistryTokenValidator {
fn validate(&self, token: &Option<String>) -> Result<(), String> {
let reg = auth_registry()
.lock()
.expect("http-json auth mutex poisoned");
if reg.tokens.is_empty() {
return Ok(());
}
let Some(token) = token else {
return Err("missing authorization token".into());
};
let candidate = trx_protocol::auth::strip_bearer(token);
if reg.tokens.contains(candidate) {
Ok(())
} else {
Err("invalid authorization token".into())
}
}
}
impl FrontendSpawner for HttpJsonFrontend {
fn spawn_frontend(
_state_rx: watch::Receiver<RigState>,
rig_tx: mpsc::Sender<RigRequest>,
_callsign: Option<String>,
listen_addr: SocketAddr,
context: std::sync::Arc<trx_frontend::FrontendRuntimeContext>,
context: Arc<FrontendRuntimeContext>,
) -> JoinHandle<()> {
tokio::spawn(async move {
if let Err(e) = serve(listen_addr, rig_tx, context).await {
@@ -86,7 +41,7 @@ impl FrontendSpawner for HttpJsonFrontend {
async fn serve(
listen_addr: SocketAddr,
rig_tx: mpsc::Sender<RigRequest>,
_context: std::sync::Arc<trx_frontend::FrontendRuntimeContext>,
context: Arc<FrontendRuntimeContext>,
) -> std::io::Result<()> {
let listener = TcpListener::bind(listen_addr).await?;
info!("json tcp frontend listening on {}", listen_addr);
@@ -96,8 +51,9 @@ async fn serve(
info!("json tcp client connected: {}", addr);
let tx_clone = rig_tx.clone();
let context = context.clone();
tokio::spawn(async move {
if let Err(e) = handle_client(socket, addr, tx_clone).await {
if let Err(e) = handle_client(socket, addr, tx_clone, context).await {
error!("json tcp client {} error: {:?}", addr, e);
}
});
@@ -108,6 +64,7 @@ async fn handle_client(
socket: TcpStream,
addr: SocketAddr,
tx: mpsc::Sender<RigRequest>,
context: Arc<FrontendRuntimeContext>,
) -> std::io::Result<()> {
let (reader, mut writer) = socket.into_split();
let mut reader = BufReader::new(reader);
@@ -143,7 +100,7 @@ async fn handle_client(
}
};
if let Err(err) = authorize(&envelope.token) {
if let Err(err) = authorize(&envelope.token, &context) {
let resp = ClientResponse {
success: false,
state: None,
@@ -215,6 +172,7 @@ async fn handle_client(
Ok(())
}
fn authorize(token: &Option<String>) -> Result<(), String> {
RegistryTokenValidator.validate(token)
fn authorize(token: &Option<String>, context: &FrontendRuntimeContext) -> Result<(), String> {
let validator = SimpleTokenValidator::new(context.auth_tokens.clone());
validator.validate(token)
}