1pub mod actions;
19pub mod wire;
20
21use crate::llm::action_helper::call_llm;
22use crate::llm::actions::protocol_trait::ActionResult;
23use crate::llm::ollama_client::OllamaClient;
24use crate::logging::emit::Log;
25use crate::protocol::Event;
26use crate::server::connection::ConnectionId;
27use crate::state::app_state::AppState;
28use anyhow::Result;
29use std::net::SocketAddr;
30use std::sync::Arc;
31use std::time::Duration;
32use tokio::io::{AsyncReadExt, AsyncWriteExt};
33use tokio::net::TcpListener;
34use tokio::sync::{mpsc, Mutex};
35use tokio_rustls::TlsAcceptor;
36
37pub use wire::{MAX_REQUEST_BYTES, MAX_URL_BYTES};
38
39const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(60);
42
43const FIRST_BYTE_TIMEOUT: Duration = Duration::from_secs(300);
49
50const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
52
53const CONNECTION_CAP_REFUSAL: &[u8] = b"";
58
59const UNAVAILABLE_RESPONSE: &[u8] = b"40 request could not be processed\r\n";
62const OVERLOADED_RESPONSE: &[u8] = b"41 backend at capacity, retry later\r\n";
63const TOO_LONG_RESPONSE: &[u8] = b"59 Request too long\r\n";
64
65const LINGER_TIME: Duration = Duration::from_secs(2);
69const LINGER_BYTES: usize = 64 * 1024;
70
71#[derive(Debug, Clone, Copy)]
73pub struct Deadlines {
74 pub handshake: Duration,
75 pub first_byte: Duration,
76}
77
78impl Deadlines {
79 pub fn new(handshake_secs: Option<u64>, first_byte_secs: Option<u64>) -> Self {
80 Self {
81 handshake: handshake_secs
82 .map(Duration::from_secs)
83 .unwrap_or(HANDSHAKE_TIMEOUT),
84 first_byte: first_byte_secs
85 .map(Duration::from_secs)
86 .unwrap_or(FIRST_BYTE_TIMEOUT),
87 }
88 }
89}
90
91pub struct GeminiServer;
92
93impl GeminiServer {
94 pub async fn spawn_with_llm_actions(
95 listen_addr: SocketAddr,
96 llm_client: OllamaClient,
97 app_state: Arc<AppState>,
98 status_tx: mpsc::UnboundedSender<String>,
99 server_id: crate::state::ServerId,
100 tls_config: Arc<rustls::ServerConfig>,
101 deadlines: Deadlines,
102 ) -> Result<SocketAddr> {
103 let listener = TcpListener::bind(listen_addr).await?;
104 let local_addr = listener.local_addr()?;
105 Log::new(Some(&status_tx)).info(format!("Gemini server listening on {}", local_addr));
106
107 let protocol = Arc::new(actions::GeminiProtocol::new());
108 let acceptor = TlsAcceptor::from(tls_config);
109 let task_registrar = app_state.clone();
110 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
111 let accept_handle = tokio::spawn(async move {
112 loop {
113 match crate::server::accept_bounded::accept_bounded(
114 &listener,
115 &limiter,
116 CONNECTION_CAP_REFUSAL,
117 "Gemini",
118 Some(&status_tx),
119 )
120 .await
121 {
122 Ok((socket, peer_addr, permit)) => {
123 let connection_id =
124 ConnectionId::new(app_state.get_next_unified_id().await);
125 use crate::state::server::{
126 ConnectionState as ServerConnectionState, ConnectionStatus,
127 ProtocolConnectionInfo,
128 };
129 let now = crate::utils::clock::Instant::now();
130 app_state
131 .add_connection_to_server(
132 server_id,
133 ServerConnectionState {
134 id: connection_id,
135 remote_addr: peer_addr,
136 local_addr,
137 bytes_sent: 0,
138 bytes_received: 0,
139 packets_sent: 0,
140 packets_received: 0,
141 last_activity: now,
142 status: ConnectionStatus::Active,
143 status_changed_at: now,
144 protocol_info: ProtocolConnectionInfo::empty(),
145 },
146 )
147 .await;
148 let _ = status_tx.send("__UPDATE_UI__".to_string());
149
150 let session = Session {
151 peer_addr,
152 llm_client: llm_client.clone(),
153 app_state: app_state.clone(),
154 status_tx: status_tx.clone(),
155 server_id,
156 protocol: protocol.clone(),
157 connection_id,
158 deadlines,
159 };
160 let acceptor = acceptor.clone();
161 let task_owner = app_state.clone();
163 task_owner
164 .spawn_server_task(server_id, async move {
165 let _permit = permit;
168 session.run(acceptor, socket).await
169 })
170 .await;
171 }
172 Err(e) => {
173 Log::new(Some(&status_tx)).error(format!("Gemini accept error: {}", e));
174 break;
175 }
176 }
177 }
178 });
179
180 task_registrar
181 .register_server_task(server_id, accept_handle)
182 .await;
183 Ok(local_addr)
184 }
185}
186
187struct Session {
188 peer_addr: SocketAddr,
189 llm_client: OllamaClient,
190 app_state: Arc<AppState>,
191 status_tx: mpsc::UnboundedSender<String>,
192 server_id: crate::state::ServerId,
193 protocol: Arc<actions::GeminiProtocol>,
194 connection_id: ConnectionId,
195 deadlines: Deadlines,
196}
197
198enum RequestRead {
200 Line(String, usize),
201 TooLong,
203 TimedOut,
204 Closed,
205}
206
207impl Session {
208 async fn run(self, acceptor: TlsAcceptor, socket: tokio::net::TcpStream) {
209 let log = Log::new(Some(&self.status_tx));
210 let tls =
211 match tokio::time::timeout(self.deadlines.handshake, acceptor.accept(socket)).await {
212 Err(_) => {
213 log.info(format!(
214 "Gemini peer {} did not complete the TLS handshake within {}s; closing",
215 self.peer_addr,
216 self.deadlines.handshake.as_secs()
217 ));
218 self.finish().await;
219 return;
220 }
221 Ok(Err(e)) => {
222 log.warn(format!(
223 "Gemini TLS handshake with {} failed: {}",
224 self.peer_addr, e
225 ));
226 self.finish().await;
227 return;
228 }
229 Ok(Ok(tls)) => tls,
230 };
231 log.info(format!("Gemini client connected from {}", self.peer_addr));
232
233 let (mut reader, write_half) = tokio::io::split(tls);
234 let write_half = Arc::new(Mutex::new(write_half));
235
236 let peer_rx = crate::server::peer_support::register_peer_channel(
240 &self.app_state,
241 self.server_id,
242 self.connection_id.as_u32(),
243 )
244 .await;
245 crate::server::peer_support::spawn_peer_command_task(
246 peer_rx,
247 self.protocol.clone(),
248 self.app_state.clone(),
249 self.server_id,
250 self.connection_id.as_u32(),
251 write_half.clone(),
252 self.status_tx.clone(),
253 );
254
255 self.exchange(&mut reader, &write_half).await;
256
257 self.app_state
258 .remove_peer_handle(self.server_id, self.connection_id.as_u32())
259 .await;
260 let _ = write_half.lock().await.shutdown().await;
262 let deadline = tokio::time::Instant::now() + LINGER_TIME;
263 let mut sink = [0u8; 4096];
264 let mut drained = 0usize;
265 while drained < LINGER_BYTES {
266 match tokio::time::timeout_at(deadline, reader.read(&mut sink)).await {
267 Ok(Ok(n)) if n > 0 => drained += n,
268 _ => break,
269 }
270 }
271 self.finish().await;
272 }
273
274 async fn finish(&self) {
275 self.app_state
276 .update_connection_status(
277 self.server_id,
278 self.connection_id,
279 crate::state::server::ConnectionStatus::Closed,
280 )
281 .await;
282 let _ = self.status_tx.send("__UPDATE_UI__".to_string());
283 }
284
285 async fn write<W>(&self, write_half: &Arc<Mutex<W>>, data: &[u8]) -> std::io::Result<()>
286 where
287 W: tokio::io::AsyncWrite + Unpin,
288 {
289 {
290 let mut writer = write_half.lock().await;
291 writer.write_all(data).await?;
292 writer.flush().await?;
293 }
294 self.app_state
295 .update_connection_stats(
296 self.server_id,
297 self.connection_id,
298 None,
299 Some(data.len() as u64),
300 None,
301 Some(1),
302 )
303 .await;
304 Ok(())
305 }
306
307 async fn read_request<R: tokio::io::AsyncRead + Unpin>(&self, reader: &mut R) -> RequestRead {
309 let mut pending: Vec<u8> = Vec::new();
310 let mut chunk = [0u8; 1024];
311 loop {
312 if let Some(idx) = pending.iter().position(|b| *b == b'\n') {
313 if idx + 1 > MAX_REQUEST_BYTES {
314 return RequestRead::TooLong;
315 }
316 let line = String::from_utf8_lossy(&pending[..idx]);
317 return RequestRead::Line(line.trim_end_matches('\r').to_string(), idx + 1);
318 }
319 if pending.len() >= MAX_REQUEST_BYTES {
320 return RequestRead::TooLong;
321 }
322 match tokio::time::timeout(self.deadlines.first_byte, reader.read(&mut chunk)).await {
323 Err(_) => return RequestRead::TimedOut,
324 Ok(Ok(0)) | Ok(Err(_)) => return RequestRead::Closed,
325 Ok(Ok(n)) => pending.extend_from_slice(&chunk[..n]),
326 }
327 }
328 }
329
330 async fn exchange<R, W>(&self, reader: &mut R, write_half: &Arc<Mutex<W>>)
331 where
332 R: tokio::io::AsyncRead + Unpin,
333 W: tokio::io::AsyncWrite + Unpin,
334 {
335 let log = Log::new(Some(&self.status_tx));
336 let (line, n) = match self.read_request(reader).await {
337 RequestRead::Line(line, n) => (line, n),
338 RequestRead::TooLong => {
339 log.warn(format!(
340 "Gemini request from {} exceeded {} bytes decision=fail_closed_request_too_long",
341 self.peer_addr, MAX_REQUEST_BYTES
342 ));
343 let _ = self.write(write_half, TOO_LONG_RESPONSE).await;
344 return;
345 }
346 RequestRead::TimedOut => {
347 log.info(format!(
348 "Gemini peer {} sent no request within {}s; closing",
349 self.peer_addr,
350 self.deadlines.first_byte.as_secs()
351 ));
352 return;
353 }
354 RequestRead::Closed => {
355 log.info(format!(
356 "Gemini peer {} closed without a request",
357 self.peer_addr
358 ));
359 return;
360 }
361 };
362 self.app_state
363 .update_connection_stats(
364 self.server_id,
365 self.connection_id,
366 Some(n as u64),
367 None,
368 Some(1),
369 None,
370 )
371 .await;
372
373 let request = match wire::parse_request(&line) {
374 Ok(request) => request,
375 Err(refusal) => {
376 log.info(format!(
377 "Gemini request from {} refused decision={}",
378 self.peer_addr,
379 refusal.decision()
380 ));
381 let _ = self.write(write_half, refusal.response().as_bytes()).await;
382 return;
383 }
384 };
385
386 let event = Event::new(
387 &actions::GEMINI_REQUEST_EVENT,
388 serde_json::json!({
389 "url": request.url,
390 "host": request.host,
391 "path": request.path,
392 "query": request.query,
393 "answer_with": actions::answer_with_for_request(
394 &request.path,
395 request.query.as_deref(),
396 ),
397 }),
398 );
399 let result = match call_llm(
400 &self.llm_client,
401 &self.app_state,
402 self.server_id,
403 Some(self.connection_id),
404 &event,
405 self.protocol.as_ref(),
406 )
407 .await
408 {
409 Ok(result) => result,
410 Err(e) => {
411 let (category, response) = match crate::utils::WireFailure::classify(&e) {
412 crate::utils::WireFailure::Overloaded => ("overloaded", OVERLOADED_RESPONSE),
413 crate::utils::WireFailure::Unavailable => ("unavailable", UNAVAILABLE_RESPONSE),
414 };
415 log.warn(format!(
416 "Gemini {} from {} decision=fail_closed_llm_error category={}",
417 request.url, self.peer_addr, category
418 ));
419 log.debug(format!("Gemini LLM call failed: {}", e));
420 let _ = self.write(write_half, response).await;
421 return;
422 }
423 };
424 for message in &result.messages {
425 log.info(message);
426 }
427
428 let mut responses: Vec<Vec<u8>> = Vec::new();
429 let mut stack = result.protocol_results;
430 stack.reverse();
431 while let Some(item) = stack.pop() {
432 match item {
433 ActionResult::Output(bytes) => responses.push(bytes),
434 ActionResult::Multiple(items) => stack.extend(items.into_iter().rev()),
435 _ => {}
436 }
437 }
438
439 let Some(response) = responses.first() else {
440 log.warn(format!(
441 "Gemini {} from {} decision=model_silent ({} failed action(s)); answering 40",
442 request.url,
443 self.peer_addr,
444 result.failures.len()
445 ));
446 let _ = self.write(write_half, UNAVAILABLE_RESPONSE).await;
447 return;
448 };
449 if responses.len() > 1 {
450 log.warn(format!(
451 "Gemini {}: the model produced {} responses to one request; sending the first",
452 request.url,
453 responses.len()
454 ));
455 }
456 let status = wire::leading_status(response).unwrap_or(0);
457 let decision = if (10..40).contains(&status) {
458 "model_answer"
459 } else {
460 "model_reject"
461 };
462 log.info(format!(
463 "Gemini {} from {} decision={} status={}",
464 request.url, self.peer_addr, decision, status
465 ));
466 if let Err(e) = self.write(write_half, response).await {
467 log.debug(format!("Gemini write to {} failed: {}", self.peer_addr, e));
468 }
469 }
470}