netget/server/tls/mod.rs
1//! TLS server implementation
2//!
3//! Provides a generic TLS transport layer that allows the LLM to implement
4//! custom application protocols on top of encrypted connections.
5
6pub mod actions;
7
8use anyhow::{Context, Result};
9use bytes::Bytes;
10use std::collections::HashMap;
11use std::net::SocketAddr;
12use std::sync::Arc;
13use tokio::io::{AsyncReadExt, AsyncWriteExt};
14use tokio::net::{TcpListener, TcpStream};
15use tokio::sync::{mpsc, Mutex};
16use tokio_rustls::TlsAcceptor;
17use tracing::{debug, error, info};
18
19use super::connection::ConnectionId;
20use crate::llm::action_helper::call_llm;
21use crate::llm::ollama_client::OllamaClient;
22use crate::llm::ActionResult;
23use crate::logging::emit::Log;
24use crate::protocol::Event;
25use crate::server::TlsProtocol;
26use crate::state::app_state::AppState;
27use crate::utils::WireFailure;
28use actions::{TLS_CONNECTION_OPENED_EVENT, TLS_DATA_RECEIVED_EVENT};
29
30/// How long a peer has to complete the TLS handshake.
31///
32/// **One constant used to govern this wait and the wait for the first application record**, on
33/// the reasoning that from the peer's side they are one condition: it holds a socket and has
34/// produced nothing usable. They are not one condition, because they face different peers.
35/// This one faces a peer that has opened a TCP socket and not yet sent a ClientHello; the
36/// other faces a peer that has *completed* a handshake. A single number cannot be right for
37/// both, and the number that was right here was wrong there.
38///
39/// Nothing on this side of the split has changed. `acceptor.accept()` was once unbounded, so a
40/// peer that connected and never sent a ClientHello held a task and a rustls state machine for
41/// as long as it liked — cheaper for an attacker than a completed connection. Every real
42/// client sends ClientHello immediately and finishes in one round-trip, and NetGet's own TLS
43/// client is no exception: `src/client/tls/mod.rs` runs `TlsConnector::connect` inside its own
44/// `connect()`, before any model turn or keystroke, so a minute is far beyond what any peer
45/// worth waiting for needs, even on a bad link. Nothing in this phase involves the model, so
46/// the deadline may cover the whole of it.
47///
48/// Overridable per server with `handshake_timeout_secs`.
49const HANDSHAKE_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60);
50
51/// How long a handshaked peer has to send its first *application* record.
52///
53/// **This was [`HANDSHAKE_READ_TIMEOUT`]'s 60 seconds, and the peer it faces is a different
54/// one.** The handshake is done here: the peer has proved it speaks TLS and has paid for a
55/// rustls state machine of its own. What it has not done is say anything on top — and TLS is a
56/// carrier, so whether it *should* have by now is a property of the application riding on it,
57/// which this server does not know.
58///
59/// What it does know is who is usually there. `src/client/tls/mod.rs` completes the handshake
60/// inside `connect()` and then writes **no application bytes at all** unprompted: every byte
61/// comes from an action, and a client created from the dashboard's `[ + tls client ]` is
62/// routed `tls_client_connected` → static-with-no-actions and then `*` → manual
63/// (`src/tui/modal/form.rs`). So it connects, handshakes, is answered with nothing, and waits
64/// for a person to type into `[ send message ]`. At 60 seconds this server hung up on the
65/// operator's own client while they were still looking at it — and, unlike the handshake
66/// phase, there was nothing the peer could have done about it.
67///
68/// 300 seconds is the window a `manual` rule gives a human to answer one event
69/// (`src/state/intercepts.rs`), which is the number this product already uses for how long
70/// someone might take, and it is also this server's own [`IDLE_AFTER_DATA_TIMEOUT`] — so a
71/// hand-driven session is now bounded the same way before its first record as after it.
72///
73/// What it costs: a peer that completed a handshake and then said nothing holds a socket, a
74/// task, an `AppState` row and one of [`MAX_CONNECTIONS`] slots for 300 seconds rather than
75/// 60 — a fivefold rise in how long one such slot is held, not a removal of the bound, and a
76/// peer over the cap is still answered [`CONNECTION_CAP_REFUSAL`]. Note that it is also
77/// strictly more expensive for the attacker than the handshake phase, which is untouched: to
78/// reach this bound at all it must complete a real TLS handshake. A listener exposed to
79/// strangers should set `first_byte_timeout_secs` low; 60 is the old value and remains a sound
80/// choice for one.
81const FIRST_RECORD_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
82
83/// The three read deadlines for one listener, resolved from its startup parameters.
84///
85/// Carried as one value so threading them from `spawn_with_llm_actions` into the connection
86/// task costs one argument rather than three.
87#[derive(Clone, Copy)]
88struct ReadDeadlines {
89 /// [`HANDSHAKE_READ_TIMEOUT`], or this server's `handshake_timeout_secs`.
90 handshake: std::time::Duration,
91 /// [`FIRST_RECORD_READ_TIMEOUT`], or this server's `first_byte_timeout_secs`.
92 first_byte: std::time::Duration,
93 /// [`IDLE_AFTER_DATA_TIMEOUT`], or this server's `idle_timeout_secs`.
94 idle: std::time::Duration,
95}
96
97impl ReadDeadlines {
98 fn resolve(
99 handshake_secs: Option<u64>,
100 first_byte_secs: Option<u64>,
101 idle_secs: Option<u64>,
102 ) -> Self {
103 Self {
104 handshake: handshake_secs
105 .map(std::time::Duration::from_secs)
106 .unwrap_or(HANDSHAKE_READ_TIMEOUT),
107 first_byte: first_byte_secs
108 .map(std::time::Duration::from_secs)
109 .unwrap_or(FIRST_RECORD_READ_TIMEOUT),
110 idle: idle_secs
111 .map(std::time::Duration::from_secs)
112 .unwrap_or(IDLE_AFTER_DATA_TIMEOUT),
113 }
114 }
115}
116
117/// How long to wait for a *further* record once the peer has sent application data.
118///
119/// TLS is a carrier, not an application: whatever rides on it decides what "idle" means, and
120/// this server cannot know. Five minutes is therefore chosen to sit well above any
121/// request/response turnaround an operator would run over it and well below an unbounded hold.
122/// Because the clock counts only *silence* — see the `activity` check at the read site — a peer
123/// waiting on a slow answer of ours is never counted against it.
124///
125/// Overridable per server with `idle_timeout_secs`, for the same reason the two before it are:
126/// the right value is a property of whatever rides on this carrier, and only the operator
127/// knows that.
128const IDLE_AFTER_DATA_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
129
130/// Concurrent connections this server admits.
131const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
132
133/// Most application data this server will hold for one connection while an answer is in flight.
134///
135/// `ConnectionData::queued_data` exists because a peer may keep writing while the model is
136/// being asked what to say — the Idle → Processing → Accumulating machine every protocol here
137/// hand-rolls. It had no ceiling, so the peer decided how much memory one connection cost, and
138/// it decided it *while the answer it is waiting for has not arrived*: the window is a whole
139/// LLM round-trip, or, where a `manual` rule parks the record for a human
140/// (`src/state/intercepts.rs`), **300 seconds by default**, times
141/// [`MAX_CONNECTIONS`]. Neither authentication nor a model call stands in front of it: the
142/// first record a stranger sends opens the window.
143///
144/// 1 MiB is 64 maximum-size TLS records (RFC 8446 caps a plaintext record at 2^14 bytes), and
145/// the number is taken from the protocol's own framing rather than from a guess about the
146/// application: the queue holds what arrived *while one answer was being composed*, and sixty
147/// four full records is already far more than any request a model is going to be asked to read.
148/// Past that the peer is not waiting for an answer, it is filling memory.
149pub const MAX_QUEUED_BYTES: usize = 1024 * 1024;
150
151/// How many further octets are read and **discarded** after the queue cap is exceeded, so the
152/// peer can finish writing and then read the alert.
153///
154/// A peer refused here is by definition mid-stream. Closing a socket with unread data in the
155/// receive queue sends `RST`, which discards the bytes already written along with it — so the
156/// close_notify record below would never arrive and the peer would see a bare connection reset
157/// instead. This is nginx's `lingering_close`; nothing is buffered, the octets are counted and
158/// dropped, and a peer that keeps writing past either bound gets the abrupt close it earned.
159const LINGER_DRAIN_BYTES: usize = 8 * 1024 * 1024;
160
161/// Wall-clock bound on that drain, so a peer trickling one octet at a time cannot hold the
162/// connection open by staying under [`LINGER_DRAIN_BYTES`].
163const LINGER_DRAIN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
164
165/// What a peer over [`MAX_CONNECTIONS`] is told before the socket closes.
166///
167/// A plaintext fatal alert record: content type 21 (alert), legacy record version 0x0303,
168/// length 2, level `fatal`(2), description `internal_error`(80). TLS defines no "server busy"
169/// alert — RFC 8446's closest is `internal_error`, which is what a server sends when it cannot
170/// proceed for reasons unrelated to the peer — so that is the honest choice. A client reports
171/// "received fatal alert: internal_error" instead of a bare connection reset, which is the
172/// difference between an operator having a reason and guessing at one.
173const CONNECTION_CAP_REFUSAL: &[u8] = &[0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x50];
174
175/// The `decision=` token for an LLM call that returned `Err`.
176///
177/// TLS has exactly one failure shape on the wire - a close_notify alert - so an overloaded
178/// backend and a broken one cannot be told apart by the peer. They are told apart in the log
179/// instead: the token is stable, so `grep 'decision=fail_closed_'` finds every connection that
180/// was hung up on because nothing usable was produced, and the `_overloaded` suffix marks the
181/// transient half. The error itself is logged, never written to the socket.
182fn llm_error_decision(err: &anyhow::Error) -> &'static str {
183 match WireFailure::classify(err) {
184 WireFailure::Overloaded => "fail_closed_llm_error_overloaded",
185 WireFailure::Unavailable => "fail_closed_llm_error_unavailable",
186 }
187}
188
189/// Connection state for LLM processing
190#[derive(Debug, Clone, PartialEq)]
191enum ConnectionState {
192 Idle,
193 Processing,
194 Accumulating,
195}
196
197/// Per-connection data for LLM handling
198struct ConnectionData {
199 state: ConnectionState,
200 queued_data: Vec<u8>,
201 write_half: Arc<Mutex<tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>>>,
202}
203
204/// TLS server that listens for incoming connections
205pub struct TlsServer;
206
207impl TlsServer {
208 /// Spawn the TLS server with integrated LLM actions
209 #[allow(clippy::too_many_arguments)]
210 pub async fn spawn_with_llm_actions(
211 listen_addr: SocketAddr,
212 llm_client: OllamaClient,
213 app_state: Arc<AppState>,
214 status_tx: mpsc::UnboundedSender<String>,
215 send_first: bool,
216 server_id: crate::state::ServerId,
217 tls_config: Option<Arc<rustls::ServerConfig>>,
218 handshake_timeout_secs: Option<u64>,
219 first_byte_timeout_secs: Option<u64>,
220 idle_timeout_secs: Option<u64>,
221 ) -> Result<SocketAddr> {
222 // All three bounds are tunable because their right value is a property of who is on
223 // the other end and what rides on this carrier, which only the operator knows. The
224 // first-record default serves NetGet's own TLS client waiting on a human; a listener
225 // exposed to strangers wants it much lower.
226 let deadlines = ReadDeadlines::resolve(
227 handshake_timeout_secs,
228 first_byte_timeout_secs,
229 idle_timeout_secs,
230 );
231 // Create TLS configuration (use provided or generate default)
232 let tls_config = if let Some(config) = tls_config {
233 config
234 } else {
235 crate::server::tls_cert_manager::generate_default_tls_config()
236 .context("Failed to generate TLS configuration")?
237 };
238
239 // Create and bind TCP listener
240 let listener = TcpListener::bind(listen_addr)
241 .await
242 .context("Failed to bind TLS TCP listener")?;
243 let local_addr = listener.local_addr()?;
244 Log::new(Some(&status_tx)).info(format!(
245 "TLS server (action-based) listening on {}",
246 local_addr
247 ));
248
249 let connections = Arc::new(Mutex::new(HashMap::new()));
250 let protocol = Arc::new(TlsProtocol::new());
251 let acceptor = TlsAcceptor::from(tls_config);
252
253 // Spawn accept loop
254 let task_registrar = app_state.clone();
255 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
256 let accept_handle = tokio::spawn(async move {
257 loop {
258 match crate::server::accept_bounded::accept_bounded(
259 &listener,
260 &limiter,
261 CONNECTION_CAP_REFUSAL,
262 "TLS",
263 Some(&status_tx),
264 )
265 .await
266 {
267 Ok((stream, remote_addr, permit)) => {
268 let connection_id =
269 ConnectionId::new(app_state.get_next_unified_id().await);
270 let local_addr_conn = stream.local_addr().unwrap_or(local_addr);
271 debug!("TLS TCP connection from {}", remote_addr);
272
273 let acceptor = acceptor.clone();
274 let llm_client_clone = llm_client.clone();
275 let app_state_clone = app_state.clone();
276 let status_tx_clone = status_tx.clone();
277 let connections_clone = connections.clone();
278 let protocol_clone = protocol.clone();
279
280 // Tracked, not detached: stop_server must abort this task too.
281 let task_owner = app_state.clone();
282 task_owner
283 .spawn_server_task(server_id, async move {
284 // Held for the life of the connection, so the cap counts live
285 // peers rather than accepts.
286 let _permit = permit;
287
288 // Perform TLS handshake, bounded. This awaits the peer's
289 // ClientHello and the rest of its side of the handshake; nothing in
290 // it involves the model, so the deadline may cover the whole of it.
291 let handshake = tokio::time::timeout(
292 deadlines.handshake,
293 acceptor.accept(stream),
294 );
295 let tls_stream = match handshake.await {
296 Err(_) => {
297 Log::new(Some(&status_tx_clone)).warn(format!(
298 "TLS handshake with {} produced nothing within {}s; \
299 closing",
300 remote_addr,
301 deadlines.handshake.as_secs()
302 ));
303 return;
304 }
305 Ok(Ok(stream)) => stream,
306 Ok(Err(e)) => {
307 // Handshake failure ends this connection but is a
308 // client-side condition, not a server error: WARN.
309 Log::new(Some(&status_tx_clone)).warn(format!(
310 "TLS handshake failed with {}: {}",
311 remote_addr, e
312 ));
313 return;
314 }
315 };
316
317 Log::new(Some(&status_tx_clone))
318 .debug(format!("TLS handshake complete with {}", remote_addr));
319
320 info!(
321 "Accepted TLS connection {} from {}",
322 connection_id, remote_addr
323 );
324
325 // Split stream
326 let (read_half, write_half) = tokio::io::split(tls_stream);
327 let write_half_arc = Arc::new(Mutex::new(write_half));
328
329 // TLS is the one server here whose read loop runs *concurrently*
330 // with the answer: `handle_data_with_actions` is spawned and the
331 // loop goes straight back to reading. So a peer whose record is
332 // parked for a human, or is waiting on the model, sits inside the
333 // read deadline while that happens — and closing it there would be
334 // exactly the live-transfer eviction this project learned about
335 // from TFTP. This tracks work in flight so the read site can tell
336 // "the peer is silent" from "the peer is waiting for us".
337 let activity = Arc::new(
338 crate::server::accept_bounded::ConnectionActivity::new(),
339 );
340
341 // Add connection to ServerInstance
342 use crate::state::server::{
343 ConnectionState as ServerConnectionState, ConnectionStatus,
344 ProtocolConnectionInfo,
345 };
346 let now = crate::utils::clock::Instant::now();
347 let conn_state = ServerConnectionState {
348 id: connection_id,
349 remote_addr,
350 local_addr: local_addr_conn,
351 bytes_sent: 0,
352 bytes_received: 0,
353 packets_sent: 0,
354 packets_received: 0,
355 last_activity: now,
356 status: ConnectionStatus::Active,
357 status_changed_at: now,
358 protocol_info: ProtocolConnectionInfo::new(serde_json::json!({
359 "state": "Idle",
360 "tls_handshake": "complete"
361 })),
362 };
363 app_state_clone
364 .add_connection_to_server(server_id, conn_state)
365 .await;
366 let _ = status_tx_clone.send("__UPDATE_UI__".to_string());
367
368 // Register the connection HERE, before either task is spawned.
369 //
370 // This used to be the first thing the banner task did, racing the
371 // reader task spawned immediately after it, and
372 // handle_data_with_actions returns silently when the connection
373 // is not in the map. TLS loses that race almost every time: the
374 // handshake reads from the socket, so application data sent right
375 // behind the client's Finished is already buffered inside rustls
376 // and the reader's first read() returns it without ever waiting on
377 // the I/O driver. 15 of 16 clients that wrote at handshake
378 // completion had their request dropped with no response and no log
379 // line.
380 // A `send_first` server registers it `Processing`:
381 // `tls_connection_opened` is being answered, and application data
382 // that arrives meanwhile queues behind it instead of raising a
383 // concurrent model call. Without `send_first` no connect event is
384 // raised, so it starts `Idle`.
385 connections_clone.lock().await.insert(
386 connection_id,
387 ConnectionData {
388 state: if send_first {
389 ConnectionState::Processing
390 } else {
391 ConnectionState::Idle
392 },
393 queued_data: Vec::new(),
394 write_half: write_half_arc.clone(),
395 },
396 );
397
398 // Peer messaging: the dashboard's "message this peer" /
399 // "disconnect this peer" inject actions into THIS connection
400 // through the same executor the LLM path uses, and the bytes
401 // go out over the same `Arc<Mutex<WriteHalf<TlsStream>>>` the
402 // session writes through - so an injected `send_tls_data` is
403 // encrypted by rustls exactly like a modelled one.
404 //
405 // Registered here, before the banner task and before the
406 // reader, for the same reason the connection map entry is:
407 // a `manual` rule can park the very first record for a human
408 // (300s by default), and that is precisely the window in
409 // which an operator needs to reach the peer. Registering it
410 // from inside the reader would lose the same race the
411 // comment above describes.
412 let peer_rx = crate::server::peer_support::register_peer_channel(
413 &app_state_clone,
414 server_id,
415 connection_id.as_u32(),
416 )
417 .await;
418 crate::server::peer_support::spawn_peer_command_task(
419 peer_rx,
420 protocol_clone.clone(),
421 app_state_clone.clone(),
422 server_id,
423 connection_id.as_u32(),
424 write_half_arc.clone(),
425 status_tx_clone.clone(),
426 );
427
428 // `tls_connection_opened` is raised only for a `send_first`
429 // server, as `tcp_connection_opened` is: the peer is owed a
430 // greeting and nothing is read until the event is answered. A
431 // server that speaks second pays no model call per connection.
432 // See `handle_connection_opened`.
433 let (opened_tx, opened_rx) = tokio::sync::oneshot::channel::<()>();
434 // Dropped by the reader on every exit path, which tells the
435 // connect task its peer is gone.
436 let (reader_alive_tx, reader_alive_rx) =
437 tokio::sync::oneshot::channel::<()>();
438 if send_first {
439 let llm_client_for_conn = llm_client_clone.clone();
440 let app_state_for_conn = app_state_clone.clone();
441 let status_tx_for_conn = status_tx_clone.clone();
442 let connections_for_conn = connections_clone.clone();
443 let write_half_for_conn = write_half_arc.clone();
444 let protocol_for_conn = protocol_clone.clone();
445 let opened_busy = activity.busy();
446 // Tracked, not detached: stop_server must abort this task too.
447 let task_owner = app_state_clone.clone();
448 task_owner
449 .spawn_server_task(server_id, async move {
450 let _opened_busy = opened_busy;
451 Self::handle_connection_opened(
452 connection_id,
453 server_id,
454 send_first,
455 opened_tx,
456 reader_alive_rx,
457 llm_client_for_conn,
458 app_state_for_conn,
459 status_tx_for_conn,
460 connections_for_conn,
461 write_half_for_conn,
462 protocol_for_conn,
463 )
464 .await;
465 })
466 .await;
467 } else {
468 drop(opened_tx);
469 drop(reader_alive_rx);
470 }
471
472 // Spawn reader task
473 let llm_client_for_read = llm_client_clone.clone();
474 let app_state_for_read = app_state_clone.clone();
475 let status_tx_for_read = status_tx_clone.clone();
476 let connections_for_read = connections_clone.clone();
477 let protocol_for_read = protocol_clone.clone();
478 let activity_for_read = Arc::clone(&activity);
479 // The reader decides the queue cap, so it needs the write half
480 // to say so: the refusal is a TLS record, and rustls will only
481 // produce one through this stream.
482 let write_half_for_read = write_half_arc.clone();
483 // Tracked, not detached: stop_server must abort this task too.
484 let task_owner = app_state_clone.clone();
485 task_owner
486 .spawn_server_task(server_id, async move {
487 let _reader_alive = reader_alive_tx;
488 let mut buffer = vec![0u8; 8192];
489 let mut read_half = read_half;
490 let mut seen_data = false;
491
492 // `send_first`: nothing is read until the connect event
493 // has been answered. Otherwise reads proceed and queue
494 // behind it (the connection starts `Processing`).
495 if send_first {
496 let _ = opened_rx.await;
497 } else {
498 drop(opened_rx);
499 }
500
501 loop {
502 let read_timeout = if seen_data {
503 deadlines.idle
504 } else {
505 deadlines.first_byte
506 };
507 // Re-arm rather than close whenever the deadline expires
508 // while an answer is still being produced: a peer waiting
509 // on us is not idle.
510 let read = loop {
511 match tokio::time::timeout(
512 read_timeout,
513 read_half.read(&mut buffer),
514 )
515 .await
516 {
517 Ok(read) => break Some(read),
518 Err(_) => {
519 if activity_for_read.idle_for().is_none() {
520 continue;
521 }
522 Log::new(Some(&status_tx_for_read)).info(
523 format!(
524 "TLS connection {connection_id} sent nothing \
525 for {}s; closing idle connection",
526 read_timeout.as_secs()
527 ),
528 );
529 break None;
530 }
531 }
532 };
533 let Some(read) = read else {
534 connections_for_read
535 .lock()
536 .await
537 .remove(&connection_id);
538 app_state_for_read
539 .close_connection_on_server(
540 server_id,
541 connection_id,
542 )
543 .await;
544 let _ = status_tx_for_read
545 .send("__UPDATE_UI__".to_string());
546 break;
547 };
548 match read {
549 Ok(0) => {
550 // Connection closed
551 connections_for_read
552 .lock()
553 .await
554 .remove(&connection_id);
555 app_state_for_read
556 .close_connection_on_server(
557 server_id,
558 connection_id,
559 )
560 .await;
561 Log::new(Some(&status_tx_for_read)).info(
562 format!(
563 "TLS connection {connection_id} closed"
564 ),
565 );
566 let _ = status_tx_for_read
567 .send("__UPDATE_UI__".to_string());
568 break;
569 }
570 Ok(n) => {
571 seen_data = true;
572
573 // Bound the queue **before** these bytes are
574 // handed to anything that would keep them.
575 // The size compared is the one the peer has
576 // already committed to — what is queued plus
577 // what this read delivered — not what is left
578 // over after some part of it has been
579 // subtracted, which is the NATS `HPUB`
580 // mistake the project CLAUDE.md records.
581 let queued_state = {
582 let conns =
583 connections_for_read.lock().await;
584 conns.get(&connection_id).map(|c| {
585 (c.state.clone(), c.queued_data.len())
586 })
587 };
588 if let Some((
589 ConnectionState::Processing,
590 queued,
591 )) = queued_state
592 {
593 if queued.saturating_add(n)
594 > MAX_QUEUED_BYTES
595 {
596 Self::refuse_queued_data_overflow(
597 connection_id,
598 server_id,
599 queued.saturating_add(n),
600 &mut read_half,
601 &write_half_for_read,
602 &connections_for_read,
603 &app_state_for_read,
604 &status_tx_for_read,
605 )
606 .await;
607 break;
608 }
609 }
610
611 let data = Bytes::copy_from_slice(&buffer[..n]);
612
613 // Keep the rail's down/up counters and
614 // `last_activity` moving. Nothing else updates
615 // them for TLS, so every connection was drawn
616 // with 0B in both directions however much it
617 // carried.
618 app_state_for_read
619 .update_connection_stats(
620 server_id,
621 connection_id,
622 Some(n as u64),
623 None,
624 Some(1),
625 None,
626 )
627 .await;
628
629 // Data summary + full payload are FileOnly: the
630 // tls_data_received event template renders the
631 // equivalent lines to the TUI, so streaming the
632 // payload here too would duplicate it and load the
633 // unbounded status channel.
634 let log = Log::new(Some(&status_tx_for_read));
635 if data.iter().all(|&b| {
636 b.is_ascii_graphic()
637 || b.is_ascii_whitespace()
638 }) {
639 let data_str =
640 String::from_utf8_lossy(&data);
641 let preview = if data_str.len() > 100 {
642 format!("{}...", &data_str[..100])
643 } else {
644 data_str.to_string()
645 };
646 log.debug(format!(
647 "TLS received {} bytes on {}: {}",
648 n, connection_id, preview
649 ));
650 log.trace(format!(
651 "TLS data (text): {:?}",
652 data_str
653 ));
654 } else {
655 log.debug(format!(
656 "TLS received {} bytes on {} (binary data)",
657 n, connection_id
658 ));
659 log.trace(format!(
660 "TLS data (hex): {}",
661 hex::encode(&data)
662 ));
663 }
664
665 // Handle data in separate task
666 let llm_clone = llm_client_for_read.clone();
667 let state_clone = app_state_for_read.clone();
668 let status_clone = status_tx_for_read.clone();
669 let conns_clone = connections_for_read.clone();
670 let protocol_clone = protocol_for_read.clone();
671 // Marked busy *before* the task is spawned, so
672 // there is no window in which the read deadline
673 // could see the connection as idle while an answer
674 // is on its way.
675 activity_for_read.begin_work();
676 let activity_for_handler =
677 Arc::clone(&activity_for_read);
678 // Tracked, not detached: stop_server must abort this task too.
679 let task_owner = app_state_for_read.clone();
680 task_owner
681 .spawn_server_task(server_id, async move {
682 Self::handle_data_with_actions(
683 connection_id,
684 server_id,
685 data,
686 llm_clone,
687 state_clone,
688 status_clone,
689 conns_clone,
690 protocol_clone,
691 )
692 .await;
693 activity_for_handler.end_work();
694 })
695 .await;
696 }
697 Err(e) => {
698 Log::new(Some(&status_tx_for_read)).error(
699 format!(
700 "Read error on {}: {}",
701 connection_id, e
702 ),
703 );
704 connections_for_read
705 .lock()
706 .await
707 .remove(&connection_id);
708 // Close it in AppState as well. Dropping only the
709 // map entry left the connection drawn as Active
710 // for the life of the server, and every later
711 // stat update targeted a peer that was gone.
712 app_state_for_read
713 .close_connection_on_server(
714 server_id,
715 connection_id,
716 )
717 .await;
718 let _ = status_tx_for_read
719 .send("__UPDATE_UI__".to_string());
720 break;
721 }
722 }
723 }
724
725 // Every exit from the read loop — EOF, the idle
726 // deadline, a read error — lands here, so the handle
727 // goes away with the connection rather than leaving
728 // the rail offering a dead peer. The peer-command
729 // task ends when the handle is dropped, releasing its
730 // clone of the write half. Idempotent with
731 // `peer_support`'s own close path, which runs when an
732 // injected `close_connection` half-closes from
733 // outside this task.
734 app_state_for_read
735 .remove_peer_handle(server_id, connection_id.as_u32())
736 .await;
737 })
738 .await;
739 })
740 .await;
741 }
742 Err(e) => {
743 Log::new(Some(&status_tx)).error(format!("Accept error: {}", e));
744 break;
745 }
746 }
747 }
748 });
749
750 task_registrar
751 .register_server_task(server_id, accept_handle)
752 .await;
753
754 Ok(local_addr)
755 }
756
757 /// Answer `tls_connection_opened` for a new connection, then release whatever the peer
758 /// sent while it was being answered. The TLS twin of `tcp`'s function of the same name,
759 /// called only for a `send_first` server, whose peer is owed a greeting: no bytes is WARN
760 /// `decision=model_silent`, and a backend failure closes with close_notify
761 /// (`decision=fail_closed_llm_error_*`). The branch for a server without `send_first` is
762 /// kept so the function stays correct if it is ever called for one.
763 ///
764 /// `opened` is signalled once the answer is on the wire; a `send_first` reader waits for
765 /// it. `reader_alive` resolves when the reader ends, which abandons the call.
766 #[allow(clippy::too_many_arguments)]
767 async fn handle_connection_opened(
768 connection_id: ConnectionId,
769 server_id: crate::state::ServerId,
770 send_first: bool,
771 opened: tokio::sync::oneshot::Sender<()>,
772 reader_alive: tokio::sync::oneshot::Receiver<()>,
773 llm_client: OllamaClient,
774 app_state: Arc<AppState>,
775 status_tx: mpsc::UnboundedSender<String>,
776 connections: Arc<Mutex<HashMap<ConnectionId, ConnectionData>>>,
777 write_half: Arc<Mutex<tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>>>,
778 protocol: Arc<TlsProtocol>,
779 ) {
780 let log = Log::new(Some(&status_tx));
781 let event = Event::new(
782 &TLS_CONNECTION_OPENED_EVENT,
783 crate::protocol::event_type::connect_event_data(),
784 );
785
786 let answer = tokio::select! {
787 answer = call_llm(
788 &llm_client,
789 &app_state,
790 server_id,
791 Some(connection_id),
792 &event,
793 protocol.as_ref(),
794 ) => answer,
795 _ = reader_alive => {
796 log.debug(format!(
797 "TLS connection {connection_id} closed before its connect event was \
798 answered: decision=peer_left_before_answer"
799 ));
800 return;
801 }
802 };
803
804 match answer {
805 Ok(execution_result) => {
806 debug!("LLM TLS connect-event response received");
807 for msg in execution_result.messages {
808 let _ = status_tx.send(msg);
809 }
810
811 let mut wrote = false;
812 for protocol_result in execution_result.protocol_results {
813 match protocol_result {
814 ActionResult::Output(output_data) => {
815 let mut write = write_half.lock().await;
816 if let Err(e) = write.write_all(&output_data).await {
817 log.error(format!("Failed to send greeting: {}", e));
818 } else {
819 drop(write);
820 log.debug(format!(
821 "TLS sent {} bytes to {}",
822 output_data.len(),
823 connection_id
824 ));
825 app_state
826 .update_connection_stats(
827 server_id,
828 connection_id,
829 None,
830 Some(output_data.len() as u64),
831 None,
832 Some(1),
833 )
834 .await;
835 wrote = true;
836 }
837 }
838 ActionResult::CloseConnection => {
839 connections.lock().await.remove(&connection_id);
840 if let Err(e) = write_half.lock().await.shutdown().await {
841 debug!("TLS shutdown on {} returned: {}", connection_id, e);
842 }
843 log.info(format!(
844 "Closed TLS connection {connection_id} on connect: \
845 decision=model_close"
846 ));
847 let _ = opened.send(());
848 return;
849 }
850 _ => {}
851 }
852 }
853
854 if wrote {
855 log.debug(format!(
856 "TLS connection {connection_id} greeted: decision=model_answer"
857 ));
858 } else if send_first {
859 log.warn(format!(
860 "TLS connection {connection_id} decision=model_silent: send_first was \
861 requested but the model produced no greeting"
862 ));
863 } else {
864 log.debug(format!(
865 "TLS connection {connection_id} connect: decision=model_no_actions"
866 ));
867 }
868 }
869 Err(e) if send_first => {
870 // A `send_first` server owes the peer a greeting; without one the peer waits
871 // for a banner that will never come. Close with a close_notify alert so it
872 // reads EOF instead. The full error goes to the log only.
873 let decision = llm_error_decision(&e);
874 error!(
875 "TLS connection {} decision={}: LLM call failed generating greeting: {:#}",
876 connection_id, decision, e
877 );
878 log.warn(format!(
879 "TLS connection {connection_id} decision={decision}: no greeting \
880 generated, closing with close_notify"
881 ));
882 {
883 let mut write = write_half.lock().await;
884 if let Err(shutdown_err) = write.shutdown().await {
885 debug!(
886 "TLS shutdown on {} returned: {}",
887 connection_id, shutdown_err
888 );
889 }
890 }
891 connections.lock().await.remove(&connection_id);
892 app_state
893 .remove_peer_handle(server_id, connection_id.as_u32())
894 .await;
895 app_state
896 .close_connection_on_server(server_id, connection_id)
897 .await;
898 let _ = opened.send(());
899 return;
900 }
901 Err(e) => {
902 // Nobody asked this server to speak first, so the peer has been failed by
903 // nothing yet; its own data gets the data path's answer.
904 log.warn(format!(
905 "TLS connect event for {connection_id} failed: \
906 decision=connect_event_failed error={e}"
907 ));
908 }
909 }
910
911 let _ = opened.send(());
912
913 let queued = {
914 let mut conns = connections.lock().await;
915 let Some(conn) = conns.get_mut(&connection_id) else {
916 return;
917 };
918 conn.state = ConnectionState::Idle;
919 !conn.queued_data.is_empty()
920 };
921 if queued {
922 Self::handle_data_with_actions(
923 connection_id,
924 server_id,
925 Bytes::new(),
926 llm_client,
927 app_state,
928 status_tx,
929 connections,
930 protocol,
931 )
932 .await;
933 }
934 }
935
936 /// Refuse a connection whose queued application data has passed [`MAX_QUEUED_BYTES`].
937 ///
938 /// **The alert is `close_notify`, and it is not the alert this deserves.** TLS has a
939 /// description for exactly this condition — `record_overflow(22)` — and rustls 0.23 cannot
940 /// send it: `CommonState::send_fatal_alert` is `pub(crate)`, and the only alert the public
941 /// API will emit is `send_close_notify`, which is what `poll_shutdown` on a
942 /// `tokio_rustls` stream calls. Writing the seven raw bytes of a `record_overflow` alert to
943 /// the TCP socket underneath is not an alternative: after the handshake every record is
944 /// encrypted, so a plaintext one is a protocol violation the peer must reject, and it would
945 /// arrive as garbage rather than as a reason. (The connection **cap** refusal higher up in
946 /// this file *is* a raw plaintext alert, and legitimately so — it is written before any
947 /// handshake has happened, when nothing is encrypted yet.)
948 ///
949 /// So the distinction the wire cannot carry is carried by the log, which is the rule the
950 /// project CLAUDE.md states for every deliberately-silent protocol: `decision=` names what
951 /// happened, and `grep decision=fail_closed` finds it.
952 #[allow(clippy::too_many_arguments)]
953 async fn refuse_queued_data_overflow<R>(
954 connection_id: ConnectionId,
955 server_id: crate::state::ServerId,
956 would_be: usize,
957 read_half: &mut R,
958 write_half: &Arc<Mutex<tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>>>,
959 connections: &Arc<Mutex<HashMap<ConnectionId, ConnectionData>>>,
960 app_state: &Arc<AppState>,
961 status_tx: &mpsc::UnboundedSender<String>,
962 ) where
963 R: tokio::io::AsyncRead + Unpin,
964 {
965 Log::new(Some(status_tx)).warn(format!(
966 "TLS connection {connection_id} decision=fail_closed_queued_data_overflow: peer \
967 queued {would_be} bytes while an answer was in flight, limit is {MAX_QUEUED_BYTES}; \
968 closing with close_notify (TLS record_overflow is not reachable through rustls)"
969 ));
970
971 {
972 let mut write = write_half.lock().await;
973 if let Err(e) = write.shutdown().await {
974 debug!(
975 "TLS shutdown on {} after overflow returned: {}",
976 connection_id, e
977 );
978 }
979 }
980
981 // Drain before the socket is dropped, or the close is an RST and the alert just
982 // written is discarded with it.
983 let mut drained = 0usize;
984 let mut scratch = vec![0u8; 64 * 1024];
985 let _ = tokio::time::timeout(LINGER_DRAIN_TIMEOUT, async {
986 while drained < LINGER_DRAIN_BYTES {
987 match read_half.read(&mut scratch).await {
988 Ok(0) | Err(_) => break,
989 Ok(n) => drained = drained.saturating_add(n),
990 }
991 }
992 })
993 .await;
994 debug!("TLS {connection_id}: drained {drained} bytes after refusing the queue");
995
996 connections.lock().await.remove(&connection_id);
997 app_state
998 .remove_peer_handle(server_id, connection_id.as_u32())
999 .await;
1000 app_state
1001 .close_connection_on_server(server_id, connection_id)
1002 .await;
1003 let _ = status_tx.send("__UPDATE_UI__".to_string());
1004 }
1005
1006 /// Handle data received on a connection with LLM actions
1007 async fn handle_data_with_actions(
1008 connection_id: ConnectionId,
1009 server_id: crate::state::ServerId,
1010 data: Bytes,
1011 llm_client: OllamaClient,
1012 app_state: Arc<AppState>,
1013 status_tx: mpsc::UnboundedSender<String>,
1014 connections: Arc<Mutex<HashMap<ConnectionId, ConnectionData>>>,
1015 protocol: Arc<TlsProtocol>,
1016 ) {
1017 // Check connection state
1018 let current_state = {
1019 let conns = connections.lock().await;
1020 if let Some(conn_data) = conns.get(&connection_id) {
1021 conn_data.state.clone()
1022 } else {
1023 // Never silent. A miss here means the connection was torn down between the
1024 // read and this lookup (peer reset, or close_this_connection on another
1025 // task) - legitimate, but indistinguishable from a registration race, which
1026 // is exactly what made the bitcoin accept-order bug so hard to find: the
1027 // read loop logged "received N bytes" and then nothing at all.
1028 debug!(
1029 "TLS connection {} is no longer registered; dropping {} received bytes",
1030 connection_id,
1031 data.len()
1032 );
1033 return;
1034 }
1035 };
1036
1037 // If processing, queue the data — up to the cap, and never past it.
1038 //
1039 // The reader loop refuses and tears the connection down before it ever hands over data
1040 // that would cross [`MAX_QUEUED_BYTES`], so this is the second half of the same bound
1041 // rather than a different one: the reader's check reads the queue length a moment
1042 // before this runs, and with a handler task in between the two there is a window in
1043 // which another read could arrive. Refusing to extend here is what makes the `Vec`
1044 // itself unable to exceed the cap, whatever that window does.
1045 if current_state == ConnectionState::Processing {
1046 let mut refused = None;
1047 connections
1048 .lock()
1049 .await
1050 .entry(connection_id)
1051 .and_modify(|conn| {
1052 let would_be = conn.queued_data.len().saturating_add(data.len());
1053 if would_be > MAX_QUEUED_BYTES {
1054 refused = Some(would_be);
1055 } else {
1056 conn.queued_data.extend_from_slice(&data);
1057 }
1058 });
1059 match refused {
1060 Some(would_be) => Log::new(Some(&status_tx)).warn(format!(
1061 "TLS connection {connection_id} decision=fail_closed_queued_data_overflow: \
1062 dropped {} bytes that would have taken the queue to {would_be}, limit is \
1063 {MAX_QUEUED_BYTES}",
1064 data.len()
1065 )),
1066 None => Log::new(Some(&status_tx)).debug(format!(
1067 "Queued {} bytes for {}",
1068 data.len(),
1069 connection_id
1070 )),
1071 }
1072 return;
1073 }
1074
1075 // Merge any queued data with new data.
1076 //
1077 // The lock was released after the state check above, so the reader task
1078 // may have removed this connection in the meantime (client disconnected).
1079 // Unwrapping here panicked the task on that race, and a panicked task
1080 // leaves the server reporting Running.
1081 let mut all_data = {
1082 let mut conns = connections.lock().await;
1083 let Some(conn_data) = conns.get_mut(&connection_id) else {
1084 return; // Connection closed while we were waiting for the lock
1085 };
1086 conn_data.state = ConnectionState::Processing;
1087 let mut merged = conn_data.queued_data.clone();
1088 merged.extend_from_slice(&data);
1089 conn_data.queued_data.clear();
1090 Bytes::from(merged)
1091 };
1092
1093 loop {
1094 // Get write_half for context
1095 let write_half = {
1096 let conns = connections.lock().await;
1097 conns.get(&connection_id).map(|c| c.write_half.clone())
1098 };
1099
1100 let Some(write_half) = write_half else {
1101 debug!(
1102 "TLS connection {} went away before its response could be written",
1103 connection_id
1104 );
1105 return;
1106 };
1107
1108 // Format data for event parameter. The encoding is reported
1109 // alongside so the model can echo binary back through
1110 // send_tls_data with encoding="hex" instead of sending the ASCII
1111 // hex digits.
1112 let printable = all_data
1113 .iter()
1114 .all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace());
1115 let (data_str, encoding) = if printable {
1116 (String::from_utf8_lossy(&all_data).to_string(), "utf8")
1117 } else {
1118 (hex::encode(&all_data), "hex")
1119 };
1120
1121 // Create data received event
1122 let event = Event::new(
1123 &TLS_DATA_RECEIVED_EVENT,
1124 serde_json::json!({
1125 "data": data_str,
1126 "encoding": encoding
1127 }),
1128 );
1129
1130 // Call LLM
1131 match call_llm(
1132 &llm_client,
1133 &app_state,
1134 server_id,
1135 Some(connection_id),
1136 &event,
1137 protocol.as_ref(),
1138 )
1139 .await
1140 {
1141 Ok(execution_result) => {
1142 debug!("LLM TLS response received");
1143
1144 // Display messages
1145 for msg in execution_result.messages {
1146 let _ = status_tx.send(msg);
1147 }
1148
1149 // Handle protocol results
1150 let mut should_close = false;
1151 let mut should_wait = false;
1152 let mut acted = false;
1153
1154 for protocol_result in execution_result.protocol_results {
1155 match protocol_result {
1156 ActionResult::Output(output_data) => {
1157 acted = true;
1158 let mut write = write_half.lock().await;
1159 let log = Log::new(Some(&status_tx));
1160 if let Err(e) = write.write_all(&output_data).await {
1161 log.error(format!("Failed to send response: {}", e));
1162 } else {
1163 // Sent-data summary + payload are FileOnly: the
1164 // send_tls_data action template already reports the send
1165 // to the TUI.
1166 if output_data
1167 .iter()
1168 .all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace())
1169 {
1170 let data_str = String::from_utf8_lossy(&output_data);
1171 let preview = if data_str.len() > 100 {
1172 format!("{}...", &data_str[..100])
1173 } else {
1174 data_str.to_string()
1175 };
1176 log.debug(format!(
1177 "TLS sent {} bytes to {}: {}",
1178 output_data.len(),
1179 connection_id,
1180 preview
1181 ));
1182 log.trace(format!("TLS sent (text): {:?}", data_str));
1183 } else {
1184 log.debug(format!(
1185 "TLS sent {} bytes to {} (binary data)",
1186 output_data.len(),
1187 connection_id
1188 ));
1189 log.trace(format!(
1190 "TLS sent (hex): {}",
1191 hex::encode(&output_data)
1192 ));
1193 }
1194 log.debug(format!(
1195 "Sent {} bytes to {}",
1196 output_data.len(),
1197 connection_id
1198 ));
1199 app_state
1200 .update_connection_stats(
1201 server_id,
1202 connection_id,
1203 None,
1204 Some(output_data.len() as u64),
1205 None,
1206 Some(1),
1207 )
1208 .await;
1209 }
1210 }
1211 ActionResult::CloseConnection => {
1212 acted = true;
1213 should_close = true;
1214 }
1215 ActionResult::WaitForMore => {
1216 acted = true;
1217 should_wait = true;
1218 }
1219 _ => {}
1220 }
1221 }
1222
1223 // An answer that carries no output, no close and no wait leaves the
1224 // peer holding an unanswered request. That is a legitimate "say nothing"
1225 // for a transport with no reply obligation, so the connection stays open -
1226 // but it is logged distinctly from a backend failure, which hangs up.
1227 if !acted {
1228 Log::new(Some(&status_tx)).warn(format!(
1229 "TLS connection {connection_id} decision=model_no_action: model \
1230 produced no usable action, nothing sent"
1231 ));
1232 }
1233
1234 // Handle wait_for_more
1235 if should_wait {
1236 connections
1237 .lock()
1238 .await
1239 .entry(connection_id)
1240 .and_modify(|conn| conn.state = ConnectionState::Accumulating);
1241 Log::new(Some(&status_tx))
1242 .debug(format!("Waiting for more data from {connection_id}"));
1243 return;
1244 }
1245
1246 // Handle close_connection.
1247 //
1248 // Dropping the map entry alone left the socket open forever
1249 // (the reader task kept blocking on read and the client never
1250 // saw a close), so close_this_connection did not close
1251 // anything. Shut the TLS write half down to send close_notify
1252 // and let the peer's read return EOF.
1253 if should_close {
1254 connections.lock().await.remove(&connection_id);
1255 if let Err(e) = write_half.lock().await.shutdown().await {
1256 debug!("TLS shutdown on {} returned: {}", connection_id, e);
1257 }
1258 Log::new(Some(&status_tx))
1259 .info(format!("Closed TLS connection {connection_id}"));
1260 return;
1261 }
1262
1263 // Check for queued data
1264 let has_queued = {
1265 let conns = connections.lock().await;
1266 conns
1267 .get(&connection_id)
1268 .map(|c| !c.queued_data.is_empty())
1269 .unwrap_or(false)
1270 };
1271
1272 if has_queued {
1273 // Take the queue and make it the next iteration's payload.
1274 // Leaving it in place re-sent the SAME bytes to the model on
1275 // every pass and never emptied the queue: one response per
1276 // iteration, forever, for a single line of input.
1277 let queued = {
1278 let mut conns = connections.lock().await;
1279 match conns.get_mut(&connection_id) {
1280 Some(conn) => std::mem::take(&mut conn.queued_data),
1281 None => return,
1282 }
1283 };
1284 if queued.is_empty() {
1285 connections
1286 .lock()
1287 .await
1288 .entry(connection_id)
1289 .and_modify(|conn| conn.state = ConnectionState::Idle);
1290 return;
1291 }
1292 all_data = Bytes::from(queued);
1293 } else {
1294 // Go to Idle state
1295 connections
1296 .lock()
1297 .await
1298 .entry(connection_id)
1299 .and_modify(|conn| conn.state = ConnectionState::Idle);
1300 return;
1301 }
1302 }
1303 Err(e) => {
1304 // The full error goes to the log and the status stream; the peer gets a
1305 // close_notify alert carrying nothing about why.
1306 let decision = llm_error_decision(&e);
1307 error!(
1308 "TLS connection {} decision={}: LLM call failed for received data: {:#}",
1309 connection_id, decision, e
1310 );
1311 Log::new(Some(&status_tx)).warn(format!(
1312 "TLS connection {connection_id} decision={decision}: no response \
1313 generated, closing with close_notify"
1314 ));
1315
1316 // Say something on the wire instead of resetting to Idle in silence.
1317 //
1318 // TLS carries no application-level error - the application protocol here
1319 // is whatever the handler invents, so there is no reply we could phrase in
1320 // it. What TLS does have is the alert protocol, and `shutdown()` emits a
1321 // real close_notify alert record (not just a FIN), which every TLS client
1322 // surfaces as a clean end of stream immediately rather than blocking until
1323 // its own timeout.
1324 //
1325 // A *fatal* `internal_error` alert would be more precise, but rustls 0.23
1326 // keeps `CommonState::send_fatal_alert` `pub(crate)`; close_notify is the
1327 // strongest in-spec signal reachable through its public API. Forging a
1328 // plaintext alert record onto the TCP socket underneath would violate
1329 // TLS 1.3 record protection, so it is not done.
1330 {
1331 let mut write = write_half.lock().await;
1332 if let Err(shutdown_err) = write.shutdown().await {
1333 debug!(
1334 "TLS shutdown on {} returned: {}",
1335 connection_id, shutdown_err
1336 );
1337 }
1338 }
1339 connections.lock().await.remove(&connection_id);
1340 // The reader task is still parked in `read()` and will only notice once
1341 // the peer closes its own side, so retire the peer handle here rather
1342 // than leaving the rail offering a connection this task has already
1343 // ended. Idempotent with the reader's own removal.
1344 app_state
1345 .remove_peer_handle(server_id, connection_id.as_u32())
1346 .await;
1347 app_state
1348 .close_connection_on_server(server_id, connection_id)
1349 .await;
1350 Log::new(Some(&status_tx)).info(format!(
1351 "Closed TLS connection {connection_id} after LLM error"
1352 ));
1353 return;
1354 }
1355 }
1356 }
1357 }
1358}