pub mod actions;
pub mod wire;
use crate::llm::action_helper::call_llm;
use crate::llm::ollama_client::OllamaClient;
use crate::logging::emit::Log;
use crate::protocol::Event;
use crate::server::connection::ConnectionId;
use crate::state::app_state::AppState;
use crate::utils::WireFailure;
use actions::{
SVN_AUTH_RESPONSE_EVENT, SVN_CLIENT_CAPABILITIES_EVENT, SVN_COMMAND_EVENT, SVN_GREETING_EVENT,
};
use anyhow::Result;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use tokio::sync::{mpsc, Mutex};
const FIRST_COMMAND_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const IDLE_BETWEEN_COMMANDS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(180);
const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
const CONNECTION_CAP_REFUSAL: &[u8] =
b"( failure ( ( 210003 28:netget: too many connections 0: 0 ) ) )\n";
pub const MAX_COMMAND_BYTES: u64 = 64 * 1024;
const COMMAND_AUTH_PREFIX: &[u8] = b"( success ( ( ) 0: ) ) ";
pub struct SvnServer;
impl SvnServer {
pub async fn spawn_with_llm_actions(
listen_addr: SocketAddr,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
) -> Result<SocketAddr> {
let listener = TcpListener::bind(listen_addr).await?;
let local_addr = listener.local_addr()?;
Log::new(Some(&status_tx)).info(format!("SVN server listening on {}", local_addr));
let protocol = Arc::new(actions::SvnProtocol::new());
let task_registrar = app_state.clone();
let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
let accept_handle = tokio::spawn(async move {
loop {
match crate::server::accept_bounded::accept_bounded(
&listener,
&limiter,
CONNECTION_CAP_REFUSAL,
"SVN",
Some(&status_tx),
)
.await
{
Ok((socket, peer_addr, permit)) => {
let connection_id =
ConnectionId::new(app_state.get_next_unified_id().await);
use crate::state::server::{
ConnectionState as ServerConnectionState, ConnectionStatus,
};
let now = crate::utils::clock::Instant::now();
let conn_state = ServerConnectionState {
id: connection_id,
remote_addr: peer_addr,
local_addr,
bytes_sent: 0,
bytes_received: 0,
packets_sent: 0,
packets_received: 0,
last_activity: now,
status: ConnectionStatus::Active,
status_changed_at: now,
protocol_info: crate::state::server::ProtocolConnectionInfo::new(
serde_json::json!({
"protocol": "svn",
"authenticated": false,
"repository_url": null,
"commands_processed": 0
}),
),
};
app_state
.add_connection_to_server(server_id, conn_state)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
Log::new(Some(&status_tx))
.info(format!("SVN client connected from {}", peer_addr));
let llm_clone = llm_client.clone();
let state_clone = app_state.clone();
let status_clone = status_tx.clone();
let protocol_clone = protocol.clone();
let connection_id_clone = connection_id;
let task_owner = app_state.clone();
task_owner
.spawn_server_task(server_id, async move {
let _permit = permit;
handle_svn_connection(
socket,
peer_addr,
llm_clone,
state_clone,
status_clone,
server_id,
protocol_clone,
connection_id_clone,
)
.await
})
.await;
}
Err(e) => {
Log::new(Some(&status_tx)).error(format!("SVN accept error: {}", e));
break;
}
}
}
});
task_registrar
.register_server_task(server_id, accept_handle)
.await;
Ok(local_addr)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
AwaitingCapabilities,
AwaitingAuthResponse,
Session,
}
#[derive(Clone, Copy)]
struct ConnRef<'a> {
app_state: &'a Arc<AppState>,
server_id: crate::state::ServerId,
connection_id: ConnectionId,
peer_addr: SocketAddr,
}
struct Delivered {
wrote: bool,
close_requested: bool,
write_failed: bool,
}
async fn deliver_results(
results: Vec<crate::llm::actions::protocol_trait::ActionResult>,
prefix: Option<&[u8]>,
write_half: &Arc<Mutex<tokio::io::WriteHalf<tokio::net::TcpStream>>>,
conn: &ConnRef<'_>,
log: &Log<'_>,
) -> Delivered {
let ConnRef {
app_state,
server_id,
connection_id,
peer_addr,
} = *conn;
use crate::llm::actions::protocol_trait::ActionResult;
let mut delivered = Delivered {
wrote: false,
close_requested: false,
write_failed: false,
};
for result in results {
match result {
ActionResult::Output(output_data) => {
let mut payload = Vec::new();
if !delivered.wrote {
if let Some(prefix) = prefix {
payload.extend_from_slice(prefix);
}
}
payload.extend_from_slice(&output_data);
delivered.wrote = true;
{
let mut writer = write_half.lock().await;
if let Err(e) = writer.write_all(&payload).await {
log.error(format!("SVN write error to {}: {}", peer_addr, e));
delivered.write_failed = true;
return delivered;
}
let _ = writer.flush().await;
}
app_state
.update_connection_stats(
server_id,
connection_id,
None,
Some(payload.len() as u64),
None,
Some(1),
)
.await;
log.debug(format!("SVN sent {} bytes to {}", payload.len(), peer_addr));
log.trace(format!(
"SVN response: {}",
String::from_utf8_lossy(&payload)
));
}
ActionResult::CloseConnection => {
delivered.close_requested = true;
}
_ => {}
}
}
delivered
}
fn capabilities_event_data(
item: &wire::Item,
client_ip: &str,
) -> (serde_json::Value, Option<String>) {
let elements: &[wire::Item] = match item {
wire::Item::List(items) => items,
_ => &[],
};
let version = match elements.first() {
Some(wire::Item::Number(n)) => *n,
_ => 0,
};
let capabilities: Vec<String> = match elements.get(1) {
Some(wire::Item::List(caps)) => caps
.iter()
.map(|c| match c {
wire::Item::Word(w) => w.clone(),
other => other.to_string(),
})
.collect(),
_ => Vec::new(),
};
let text = |i: Option<&wire::Item>| -> Option<String> {
match i {
Some(wire::Item::Str(bytes)) => Some(String::from_utf8_lossy(bytes).into_owned()),
Some(wire::Item::Word(w)) => Some(w.clone()),
_ => None,
}
};
let url = text(elements.get(2));
let ra_client = text(elements.get(3));
(
serde_json::json!({
"version": version,
"capabilities": capabilities,
"url": url.clone().unwrap_or_default(),
"ra_client": ra_client.unwrap_or_default(),
"client_ip": client_ip,
}),
url,
)
}
fn auth_event_data(item: &wire::Item, url: Option<&str>, client_ip: &str) -> serde_json::Value {
let elements: &[wire::Item] = match item {
wire::Item::List(items) => items,
_ => &[],
};
let mechanism = match elements.first() {
Some(wire::Item::Word(w)) => w.clone(),
Some(other) => other.to_string(),
None => String::new(),
};
let token = match elements.get(1) {
Some(wire::Item::List(inner)) => match inner.first() {
Some(wire::Item::Str(bytes)) => String::from_utf8_lossy(bytes).trim().to_string(),
Some(other) => other.to_string(),
None => String::new(),
},
Some(wire::Item::Str(bytes)) => String::from_utf8_lossy(bytes).trim().to_string(),
_ => String::new(),
};
serde_json::json!({
"mechanism": mechanism,
"token": token,
"url": url.unwrap_or_default(),
"client_ip": client_ip,
})
}
pub fn command_event_data(item: &wire::Item, client_ip: &str) -> (String, serde_json::Value) {
let elements: &[wire::Item] = match item {
wire::Item::List(items) => items,
_ => std::slice::from_ref(item),
};
let command = match elements.first() {
Some(wire::Item::Word(w)) => w.clone(),
Some(other) => other.to_string(),
None => String::new(),
};
let rest = elements.get(1..).unwrap_or(&[]);
let args: Vec<serde_json::Value> = match rest {
[wire::Item::List(params)] => params.iter().map(wire::Item::to_json).collect(),
other => other.iter().map(wire::Item::to_json).collect(),
};
(
command.clone(),
serde_json::json!({
"command_line": item.to_string(),
"command": command,
"args": args,
"client_ip": client_ip,
}),
)
}
fn looks_like_auth_response(item: &wire::Item) -> bool {
match item.head() {
Some(wire::Item::Word(word)) => {
!word.is_empty() && !word.chars().any(|c| c.is_ascii_lowercase())
}
_ => false,
}
}
async fn handle_svn_connection(
socket: tokio::net::TcpStream,
peer_addr: SocketAddr,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
protocol: Arc<actions::SvnProtocol>,
connection_id: ConnectionId,
) {
let (reader, write_half) = tokio::io::split(socket);
let write_half = Arc::new(Mutex::new(write_half));
let mut items = wire::ItemReader::new(reader);
let log = Log::new(Some(&status_tx));
let client_ip = peer_addr.ip().to_string();
let conn = ConnRef {
app_state: &app_state,
server_id,
connection_id,
peer_addr,
};
let peer_rx = crate::server::peer_support::register_peer_channel(
&app_state,
server_id,
connection_id.as_u32(),
)
.await;
crate::server::peer_support::spawn_peer_command_task(
peer_rx,
protocol.clone(),
app_state.clone(),
server_id,
connection_id.as_u32(),
write_half.clone(),
status_tx.clone(),
);
let greeting_event = Event::new(
&SVN_GREETING_EVENT,
serde_json::json!({ "client_ip": client_ip }),
);
log.debug(format!("SVN sending greeting to {}", peer_addr));
match call_llm(
&llm_client,
&app_state,
server_id,
Some(connection_id),
&greeting_event,
protocol.as_ref(),
)
.await
{
Ok(execution_result) => {
for message in &execution_result.messages {
log.info(message);
}
let delivered = deliver_results(
execution_result.protocol_results,
None,
&write_half,
&conn,
&log,
)
.await;
if delivered.write_failed {
app_state
.remove_peer_handle(server_id, connection_id.as_u32())
.await;
return;
}
if delivered.close_requested {
log.info(format!(
"SVN greeting from {} decision=model_close",
peer_addr
));
close_svn_connection(&app_state, server_id, connection_id, &status_tx).await;
return;
}
if !delivered.wrote {
log.warn(format!(
"SVN greeting from {} decision=no_action (nothing written; peer is \
waiting for a greeting)",
peer_addr
));
}
}
Err(e) => {
let failure = WireFailure::classify(&e);
log.error(format!(
"SVN greeting for {} decision=fail_closed_llm_error category={} error: {}",
peer_addr,
if failure.is_overloaded() {
"overloaded"
} else {
"unavailable"
},
e
));
write_svn_failure(
&write_half,
&app_state,
server_id,
connection_id,
&log,
failure,
None,
)
.await;
close_svn_connection(&app_state, server_id, connection_id, &status_tx).await;
return;
}
}
let mut phase = Phase::AwaitingCapabilities;
let mut ra_svn_session = false;
let mut session_url: Option<String> = None;
let mut answered_one = false;
loop {
let read_timeout = if answered_one {
IDLE_BETWEEN_COMMANDS_TIMEOUT
} else {
FIRST_COMMAND_READ_TIMEOUT
};
let read =
match tokio::time::timeout(read_timeout, items.read_item(MAX_COMMAND_BYTES)).await {
Ok(read) => read,
Err(_) => {
log.info(format!(
"SVN client {} sent nothing for {}s; closing idle connection",
peer_addr,
read_timeout.as_secs()
));
break;
}
};
answered_one = true;
let (item, consumed) = match read {
Ok(Some(message)) => message,
Ok(None) => {
log.info(format!("SVN client {} disconnected", peer_addr));
break;
}
Err(wire::WireError::Io(e)) => {
log.error(format!("SVN read error from {}: {}", peer_addr, e));
break;
}
Err(wire::WireError::UnexpectedEof) => {
log.info(format!(
"SVN client {} closed the connection mid-tuple",
peer_addr
));
break;
}
Err(e) => {
log.warn(format!(
"SVN refused a message from {} decision=fail_closed_framing: {}",
peer_addr, e
));
write_svn_malformed(&write_half, &app_state, server_id, connection_id, &log).await;
break;
}
};
app_state
.update_connection_stats(
server_id,
connection_id,
Some(consumed),
None,
Some(1),
None,
)
.await;
log.debug(format!(
"SVN received {} bytes from {}",
consumed, peer_addr
));
log.trace(format!("SVN message: {}", item));
let looks_like_capabilities = matches!(item.head(), Some(wire::Item::Number(_)));
let (event, label, prefix): (Event, String, Option<&[u8]>) = match phase {
Phase::AwaitingCapabilities if looks_like_capabilities => {
let (data, url) = capabilities_event_data(&item, &client_ip);
session_url = url;
phase = Phase::AwaitingAuthResponse;
(
Event::new(&SVN_CLIENT_CAPABILITIES_EVENT, data),
"capabilities".to_string(),
None,
)
}
Phase::AwaitingAuthResponse if looks_like_auth_response(&item) => {
let data = auth_event_data(&item, session_url.as_deref(), &client_ip);
phase = Phase::Session;
ra_svn_session = true;
(
Event::new(&SVN_AUTH_RESPONSE_EVENT, data),
"auth-response".to_string(),
None,
)
}
_ => {
if phase == Phase::AwaitingAuthResponse {
ra_svn_session = true;
}
phase = Phase::Session;
let (command, data) = command_event_data(&item, &client_ip);
(
Event::new(&SVN_COMMAND_EVENT, data),
command,
if ra_svn_session {
Some(COMMAND_AUTH_PREFIX)
} else {
None
},
)
}
};
log.debug(format!("SVN calling LLM for {} from {}", label, peer_addr));
match call_llm(
&llm_client,
&app_state,
server_id,
Some(connection_id),
&event,
protocol.as_ref(),
)
.await
{
Ok(execution_result) => {
for message in &execution_result.messages {
log.info(message);
}
log.debug(format!(
"SVN got {} protocol results",
execution_result.protocol_results.len()
));
let delivered = deliver_results(
execution_result.protocol_results,
prefix,
&write_half,
&conn,
&log,
)
.await;
if delivered.write_failed {
app_state
.remove_peer_handle(server_id, connection_id.as_u32())
.await;
return;
}
if delivered.close_requested {
log.debug(format!(
"SVN '{}' from {} decision=model_close",
label, peer_addr
));
break;
}
if !delivered.wrote {
log.warn(format!(
"SVN '{}' from {} decision=no_action (nothing written)",
label, peer_addr
));
}
}
Err(e) => {
let failure = WireFailure::classify(&e);
log.error(format!(
"SVN '{}' from {} decision=fail_closed_llm_error category={} error: {}",
label,
peer_addr,
if failure.is_overloaded() {
"overloaded"
} else {
"unavailable"
},
e
));
write_svn_failure(
&write_half,
&app_state,
server_id,
connection_id,
&log,
failure,
prefix,
)
.await;
break;
}
}
}
use crate::state::server::ConnectionStatus;
app_state
.remove_peer_handle(server_id, connection_id.as_u32())
.await;
app_state
.update_connection_status(server_id, connection_id, ConnectionStatus::Closed)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
}
fn svn_failure_tuple(failure: WireFailure) -> Vec<u8> {
let error_code = if failure.is_overloaded() {
210003
} else {
210000
};
let message = failure.text();
format!(
"( failure ( ( {} {}:{} 0: 0 ) ) )\n",
error_code,
message.len(),
message
)
.into_bytes()
}
async fn write_svn_failure(
write_half: &Arc<Mutex<tokio::io::WriteHalf<tokio::net::TcpStream>>>,
app_state: &Arc<AppState>,
server_id: crate::state::ServerId,
connection_id: ConnectionId,
log: &Log<'_>,
failure: WireFailure,
prefix: Option<&[u8]>,
) {
let mut payload = prefix.unwrap_or(&[]).to_vec();
payload.extend_from_slice(&svn_failure_tuple(failure));
let written = {
let mut writer = write_half.lock().await;
match writer.write_all(&payload).await {
Ok(()) => {
let _ = writer.flush().await;
true
}
Err(e) => {
log.debug(format!("SVN could not write failure tuple: {}", e));
false
}
}
};
if written {
app_state
.update_connection_stats(
server_id,
connection_id,
None,
Some(payload.len() as u64),
None,
Some(1),
)
.await;
}
}
const MALFORMED_REFUSAL: &[u8] =
b"( failure ( ( 210004 33:netget: refused malformed message 0: 0 ) ) )\n";
async fn write_svn_malformed(
write_half: &Arc<Mutex<tokio::io::WriteHalf<tokio::net::TcpStream>>>,
app_state: &Arc<AppState>,
server_id: crate::state::ServerId,
connection_id: ConnectionId,
log: &Log<'_>,
) {
let written = {
let mut writer = write_half.lock().await;
match writer.write_all(MALFORMED_REFUSAL).await {
Ok(()) => {
let _ = writer.flush().await;
true
}
Err(e) => {
log.debug(format!("SVN could not write refusal tuple: {}", e));
false
}
}
};
if written {
app_state
.update_connection_stats(
server_id,
connection_id,
None,
Some(MALFORMED_REFUSAL.len() as u64),
None,
Some(1),
)
.await;
}
}
async fn close_svn_connection(
app_state: &Arc<AppState>,
server_id: crate::state::ServerId,
connection_id: ConnectionId,
status_tx: &mpsc::UnboundedSender<String>,
) {
use crate::state::server::ConnectionStatus;
app_state
.remove_peer_handle(server_id, connection_id.as_u32())
.await;
app_state
.update_connection_status(server_id, connection_id, ConnectionStatus::Closed)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
}