1pub mod actions;
3pub mod cluster;
4pub mod pubsub;
5pub mod resp3;
6pub mod resp_value;
7
8use crate::llm::action_helper::call_llm;
9use crate::llm::actions::protocol_trait::ActionResult;
10use crate::llm::ollama_client::OllamaClient;
11use crate::logging::emit::Log;
12use crate::protocol::Event;
13use crate::server::connection::ConnectionId;
14use crate::state::app_state::AppState;
15use crate::utils::redis_keys::{key_hash_slot, slot_keys};
16use crate::utils::resp::{scan_resp2_frame, RespLength, RespLimits, RespScan};
17use actions::{
18 encode_error, encode_integer, RedisProtocol, ACCEPT_SUBSCRIPTION, DELIVER_MESSAGE,
19 REDIS_COMMAND_EVENT, REDIS_PUBLISH_EVENT, REDIS_SUBSCRIBE_EVENT,
20};
21use anyhow::Result;
22use pubsub::{Kind, Outbox, OutboxReceiver, PubSubHub};
23use redis_protocol::resp2::decode::decode;
24use redis_protocol::resp2::types::OwnedFrame as Frame;
25use resp_value::Resp;
26use std::net::SocketAddr;
27use std::sync::atomic::Ordering;
28use std::sync::Arc;
29use tokio::io::{AsyncReadExt, AsyncWriteExt};
30use tokio::net::{TcpListener, TcpStream};
31use tokio::sync::mpsc;
32use tracing::{debug, error, trace, warn};
33
34pub const MAX_PENDING_FRAME_BYTES: usize = 64 * 1024 * 1024;
41
42const RESP_LIMITS: RespLimits = RespLimits::with_max_bulk_len(MAX_PENDING_FRAME_BYTES);
47
48const FIRST_COMMAND_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
81
82const IDLE_BETWEEN_COMMANDS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
94
95const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
100
101const CONNECTION_CAP_REFUSAL: &[u8] = b"-ERR max number of clients reached\r\n";
108
109pub struct RedisShared {
113 pub hub: PubSubHub,
114 pub cluster: Option<std::sync::Mutex<cluster::ClusterState>>,
115}
116
117impl RedisShared {
118 fn cluster(&self) -> Option<std::sync::MutexGuard<'_, cluster::ClusterState>> {
119 self.cluster
120 .as_ref()
121 .map(|c| c.lock().unwrap_or_else(|p| p.into_inner()))
122 }
123}
124
125#[derive(Debug, Default, Clone)]
127pub struct RedisServerOptions {
128 pub first_byte_timeout_secs: Option<u64>,
129 pub idle_timeout_secs: Option<u64>,
130 pub cluster_nodes: Option<serde_json::Value>,
132}
133
134pub struct RedisServer {
136 llm_client: OllamaClient,
137 app_state: Arc<AppState>,
138 #[allow(dead_code)]
139 status_tx: mpsc::UnboundedSender<String>,
140 server_id: Option<crate::state::ServerId>,
141}
142
143impl RedisServer {
144 pub fn new(
146 llm_client: OllamaClient,
147 app_state: Arc<AppState>,
148 status_tx: mpsc::UnboundedSender<String>,
149 server_id: Option<crate::state::ServerId>,
150 ) -> Self {
151 Self {
152 llm_client,
153 app_state,
154 status_tx,
155 server_id,
156 }
157 }
158
159 pub async fn spawn_with_llm_actions(
161 listen_addr: SocketAddr,
162 llm_client: OllamaClient,
163 app_state: Arc<AppState>,
164 status_tx: mpsc::UnboundedSender<String>,
165 _send_first: bool,
166 server_id: crate::state::ServerId,
167 options: RedisServerOptions,
168 ) -> Result<SocketAddr> {
169 let first_byte_timeout = options
173 .first_byte_timeout_secs
174 .map(std::time::Duration::from_secs)
175 .unwrap_or(FIRST_COMMAND_READ_TIMEOUT);
176 let idle_timeout = options
177 .idle_timeout_secs
178 .map(std::time::Duration::from_secs)
179 .unwrap_or(IDLE_BETWEEN_COMMANDS_TIMEOUT);
180 let listener = TcpListener::bind(listen_addr).await?;
181 let actual_addr = listener.local_addr()?;
182
183 let cluster = options
187 .cluster_nodes
188 .as_ref()
189 .map(|nodes| cluster::ClusterState::from_param(nodes, actual_addr))
190 .transpose()
191 .map_err(|e| anyhow::anyhow!("invalid cluster_nodes: {e:#}"))?;
192 let shared = Arc::new(RedisShared {
193 hub: PubSubHub::default(),
194 cluster: cluster.map(std::sync::Mutex::new),
195 });
196 app_state
197 .register_server_handle(server_id, shared.clone())
198 .await;
199
200 let log = Log::new(Some(&status_tx));
201 match shared.cluster() {
202 Some(c) => log.info(format!(
203 "Redis server listening on {} (cluster mode: {} of {} nodes)",
204 actual_addr,
205 c.me().addr(),
206 c.nodes.len()
207 )),
208 None => log.info(format!("Redis server listening on {}", actual_addr)),
209 }
210
211 let server = Arc::new(RedisServer::new(
212 llm_client,
213 app_state.clone(),
214 status_tx.clone(),
215 Some(server_id),
216 ));
217
218 let status_tx_clone = status_tx.clone();
219 let task_registrar = app_state.clone();
220
221 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
223 let accept_handle = tokio::spawn(async move {
224 loop {
225 match crate::server::accept_bounded::accept_bounded(
226 &listener,
227 &limiter,
228 CONNECTION_CAP_REFUSAL,
229 "Redis",
230 Some(&status_tx),
231 )
232 .await
233 {
234 Ok((stream, addr, permit)) => {
235 Log::new(Some(&status_tx)).info(format!("Redis connection from {}", addr));
236
237 let connection_id =
238 ConnectionId::new(app_state.get_next_unified_id().await);
239 let local_addr_conn = stream.local_addr().unwrap_or(actual_addr);
240
241 if let Some(server_id) = server.server_id {
243 use crate::state::server::{
244 ConnectionState as ServerConnectionState, ConnectionStatus,
245 ProtocolConnectionInfo,
246 };
247 let now = crate::utils::clock::Instant::now();
248 let conn_state = ServerConnectionState {
249 id: connection_id,
250 remote_addr: addr,
251 local_addr: local_addr_conn,
252 bytes_sent: 0,
253 bytes_received: 0,
254 packets_sent: 0,
255 packets_received: 0,
256 last_activity: now,
257 status: ConnectionStatus::Active,
258 status_changed_at: now,
259 protocol_info: ProtocolConnectionInfo::empty(),
260 };
261 server
262 .app_state
263 .add_connection_to_server(server_id, conn_state)
264 .await;
265 }
266
267 let handler = RedisHandler {
268 connection_id,
269 llm_client: server.llm_client.clone(),
270 app_state: server.app_state.clone(),
271 status_tx: status_tx.clone(),
272 server_id: server.server_id,
273 first_byte_timeout,
274 idle_timeout,
275 shared: shared.clone(),
276 };
277
278 let conn_handle = tokio::spawn(async move {
279 let _permit = permit;
283 if let Err(e) = handler.handle_connection(stream).await {
284 error!("Redis connection error: {:?}", e);
285 }
286 });
287
288 if let Some(server_id) = server.server_id {
294 server
295 .app_state
296 .register_server_task(server_id, conn_handle)
297 .await;
298 }
299 }
300 Err(e) => {
301 Log::new(Some(&status_tx)).error(format!("Redis accept error: {}", e));
302 }
303 }
304 }
305 });
306
307 task_registrar
309 .register_server_task(server_id, accept_handle)
310 .await;
311
312 let _ = status_tx_clone.send("__UPDATE_UI__".to_string());
313 Ok(actual_addr)
314 }
315}
316
317struct RedisHandler {
319 connection_id: ConnectionId,
320 llm_client: OllamaClient,
321 app_state: Arc<AppState>,
322 status_tx: mpsc::UnboundedSender<String>,
323 server_id: Option<crate::state::ServerId>,
324 first_byte_timeout: std::time::Duration,
326 idle_timeout: std::time::Duration,
328 shared: Arc<RedisShared>,
329}
330
331struct AbortWriter(tokio::task::AbortHandle);
335
336impl Drop for AbortWriter {
337 fn drop(&mut self) {
338 self.0.abort();
339 }
340}
341
342#[derive(Default)]
344struct ModelAnswer {
345 output: Vec<u8>,
347 close: bool,
348 accept: bool,
350 deliver: bool,
352 failed: bool,
354}
355
356struct Conn<'a> {
358 protocol: Arc<RedisProtocol>,
359 outbox: Outbox,
360 write_half: Arc<tokio::sync::Mutex<tokio::io::WriteHalf<TcpStream>>>,
361 log: Log<'a>,
362 asking: bool,
364}
365
366enum Flow {
368 Continue,
369 Close,
370}
371
372const SUBSCRIBED_MODE_COMMANDS: &[&str] = &[
374 "SUBSCRIBE",
375 "PSUBSCRIBE",
376 "SSUBSCRIBE",
377 "UNSUBSCRIBE",
378 "PUNSUBSCRIBE",
379 "SUNSUBSCRIBE",
380 "PING",
381 "QUIT",
382 "RESET",
383];
384
385fn wrong_arity(name: &str) -> String {
386 format!(
387 "ERR wrong number of arguments for '{}' command",
388 name.to_ascii_lowercase()
389 )
390}
391
392impl RedisHandler {
393 async fn handle_connection(self, stream: TcpStream) -> Result<()> {
398 let server_id = self.server_id;
399 let connection_id = self.connection_id;
400 let app_state = self.app_state.clone();
401 let status_tx = self.status_tx.clone();
402 let shared = self.shared.clone();
403
404 let result = self.run(stream).await;
405
406 shared.hub.remove_connection(connection_id.as_u32());
408
409 if let Some(server_id) = server_id {
413 app_state
414 .remove_peer_handle(server_id, connection_id.as_u32())
415 .await;
416 app_state
417 .close_connection_on_server(server_id, connection_id)
418 .await;
419 let _ = status_tx.send("__UPDATE_UI__".to_string());
420 }
421
422 result
423 }
424
425 async fn refuse(&self, conn: &Conn<'_>, message: &str) {
429 let _ = conn.outbox.send_reply(encode_error(message)).await;
430 let _ = conn.write_half.lock().await.shutdown().await;
431 }
432
433 async fn write_reply(&self, conn: &Conn<'_>, reply: Vec<u8>) -> Result<()> {
436 conn.outbox.send_reply(reply).await?;
437 Ok(())
438 }
439
440 async fn writer_task(
442 mut out: OutboxReceiver,
443 outbox: Outbox,
444 write_half: Arc<tokio::sync::Mutex<tokio::io::WriteHalf<TcpStream>>>,
445 app_state: Arc<AppState>,
446 server_id: Option<crate::state::ServerId>,
447 connection_id: ConnectionId,
448 ) {
449 while let Some(frame) = out.rx.recv().await {
450 let len = frame.bytes.len();
451 let result = if len == 0 {
452 Ok(())
455 } else {
456 let mut writer = write_half.lock().await;
457 match writer.write_all(&frame.bytes).await {
458 Ok(()) => writer.flush().await,
459 Err(e) => Err(e),
460 }
461 };
462 if frame.counted {
463 out.queued.fetch_sub(len, Ordering::AcqRel);
464 }
465 let failed = result.is_err();
466 if !failed && len > 0 {
467 if let Some(server_id) = server_id {
468 app_state
469 .update_connection_stats(
470 server_id,
471 connection_id,
472 None,
473 Some(len as u64),
474 None,
475 Some(1),
476 )
477 .await;
478 }
479 }
480 if let Some(ack) = frame.ack {
481 let _ = ack.send(result);
482 }
483 if failed {
484 outbox.mark_killed();
485 break;
486 }
487 }
488 }
489
490 async fn run(self, stream: TcpStream) -> Result<()> {
491 let first_byte_timeout = self.first_byte_timeout;
493 let idle_timeout = self.idle_timeout;
494 let protocol = Arc::new(RedisProtocol::for_connection(
495 self.connection_id,
496 self.app_state.clone(),
497 self.status_tx.clone(),
498 self.shared.clone(),
499 ));
500
501 let (mut read_half, write_half) = tokio::io::split(stream);
504 let write_half = Arc::new(tokio::sync::Mutex::new(write_half));
505
506 if let Some(server_id) = self.server_id {
514 let peer_rx = crate::server::peer_support::register_peer_channel(
515 &self.app_state,
516 server_id,
517 self.connection_id.as_u32(),
518 )
519 .await;
520 crate::server::peer_support::spawn_peer_command_task(
521 peer_rx,
522 protocol.clone(),
523 self.app_state.clone(),
524 server_id,
525 self.connection_id.as_u32(),
526 write_half.clone(),
527 self.status_tx.clone(),
528 );
529 let _ = self.status_tx.send("__UPDATE_UI__".to_string());
530 }
531
532 let (outbox, outbox_rx) = Outbox::new();
535 let writer = tokio::spawn(Self::writer_task(
536 outbox_rx,
537 outbox.clone(),
538 write_half.clone(),
539 self.app_state.clone(),
540 self.server_id,
541 self.connection_id,
542 ));
543 let _writer_guard = AbortWriter(writer.abort_handle());
544 if let Some(server_id) = self.server_id {
545 self.app_state.register_server_task(server_id, writer).await;
546 }
547
548 let mut conn = Conn {
549 protocol,
550 outbox,
551 write_half,
552 log: Log::new(Some(&self.status_tx)),
553 asking: false,
554 };
555
556 let mut buffer = Vec::new();
557 let mut answered_one = false;
558
559 loop {
560 let read_timeout = if answered_one {
569 idle_timeout
570 } else {
571 first_byte_timeout
572 };
573 let mut chunk = vec![0u8; 4096];
574 let read = tokio::select! {
575 read = tokio::time::timeout(read_timeout, read_half.read(&mut chunk)) => read,
576 _ = conn.outbox.closing() => {
578 conn.log.warn(format!(
579 "Redis connection {} closed: it stopped reading while {} bytes of \
580 pub/sub messages were queued for it",
581 self.connection_id,
582 conn.outbox.queued_bytes()
583 ));
584 return Ok(());
585 }
586 };
587 let read = match read {
588 Ok(read) => read,
589 Err(_) => {
590 conn.log.info(format!(
591 "Redis client {} sent nothing for {}s; closing idle connection",
592 self.connection_id,
593 read_timeout.as_secs()
594 ));
595 return Ok(());
596 }
597 };
598 let n = match read {
599 Ok(0) => {
600 debug!("Redis client disconnected");
601 return Ok(());
602 }
603 Ok(n) => n,
604 Err(e) => {
605 error!("Redis read error: {}", e);
606 return Err(e.into());
607 }
608 };
609
610 buffer.extend_from_slice(&chunk[..n]);
611
612 if let Some(server_id) = self.server_id {
613 self.app_state
614 .update_connection_stats(
615 server_id,
616 self.connection_id,
617 Some(n as u64),
618 None,
619 Some(1),
620 None,
621 )
622 .await;
623 }
624
625 if buffer.len() > MAX_PENDING_FRAME_BYTES {
626 conn.log.error(format!(
627 "Redis client {} exceeded the {} byte frame buffer limit without completing a frame; closing",
628 self.connection_id, MAX_PENDING_FRAME_BYTES
629 ));
630 self.refuse(&conn, "ERR Protocol error: invalid multibulk length")
631 .await;
632 return Ok(());
633 }
634
635 let mut offset = 0;
637 while offset < buffer.len() {
638 match scan_resp2_frame(&buffer[offset..], &RESP_LIMITS) {
643 RespScan::Complete { .. } => {}
644 RespScan::Incomplete => break,
645 RespScan::TooDeep { limit } => {
646 conn.log.warn(format!(
647 "Redis connection {} decision=fail_closed_resp_too_deep: frame nests \
648 arrays deeper than {} levels; refusing and closing",
649 self.connection_id, limit
650 ));
651 self.refuse(&conn, "ERR Protocol error: nesting too deep")
652 .await;
653 return Ok(());
654 }
655 RespScan::TooLong(kind) => {
656 let (what, reply) = match kind {
657 RespLength::Aggregate => (
658 "array length",
659 "ERR Protocol error: invalid multibulk length",
660 ),
661 RespLength::Bulk => {
662 ("bulk length", "ERR Protocol error: invalid bulk length")
663 }
664 };
665 conn.log.warn(format!(
666 "Redis connection {} decision=fail_closed_resp_too_long: declared {} \
667 exceeds the frame limit; refusing and closing",
668 self.connection_id, what
669 ));
670 self.refuse(&conn, reply).await;
671 return Ok(());
672 }
673 RespScan::Malformed => {
674 error!(
677 "Redis connection {} sent a frame that is not RESP2; closing",
678 self.connection_id
679 );
680 return Err(anyhow::anyhow!("undecodable RESP frame"));
681 }
682 }
683 match decode(&buffer[offset..]) {
684 Ok(Some((frame, consumed))) => {
685 answered_one = true;
688 trace!("Redis frame: {:?}", frame);
689 offset += consumed;
690 match self.handle_frame(&mut conn, &frame).await? {
691 Flow::Continue => {}
692 Flow::Close => return Ok(()),
693 }
694 }
695 Ok(None) => {
696 break;
698 }
699 Err(e) => {
700 error!("Redis decode error: {:?}", e);
702 return Err(e.into());
703 }
704 }
705 }
706
707 buffer.drain(..offset);
709 }
710 }
711
712 async fn handle_frame(&self, conn: &mut Conn<'_>, frame: &Frame) -> Result<Flow> {
719 let args = frame_args(frame);
720 let name = args
721 .first()
722 .map(|a| String::from_utf8_lossy(a).to_ascii_uppercase())
723 .unwrap_or_default();
724 let conn_key = self.connection_id.as_u32();
725 let proto = conn.protocol.protocol_version();
726 let asking = std::mem::take(&mut conn.asking);
728
729 let route = self.shared.cluster().map(|cluster| {
732 let slots: Vec<u16> = slot_keys(&args).iter().map(|k| key_hash_slot(k)).collect();
733 cluster.route(&slots, asking)
734 });
735 if let Some(route) = route {
736 if let Some(error) = route.error() {
737 debug!(
738 "Redis connection {} decision=cluster_redirect command={} {}",
739 self.connection_id, name, error
740 );
741 self.write_reply(conn, encode_error(&error)).await?;
742 return Ok(Flow::Continue);
743 }
744 }
745
746 if proto == 2
748 && self.shared.hub.subscription_count(conn_key) > 0
749 && !SUBSCRIBED_MODE_COMMANDS.contains(&name.as_str())
750 {
751 self.write_reply(
752 conn,
753 encode_error(&format!(
754 "ERR Can't execute '{}': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context",
755 name.to_ascii_lowercase()
756 )),
757 )
758 .await?;
759 return Ok(Flow::Continue);
760 }
761
762 let command_str = frame_to_command_string(frame);
763 let command_for_log = crate::utils::truncate_for_log(&command_str, 512);
768 conn.log
769 .debug(format!("Redis command: {}", command_for_log));
770 let subscribed_resp2 = proto == 2 && self.shared.hub.subscription_count(conn_key) > 0;
771
772 match name.as_str() {
773 "HELLO" => {
774 return self
775 .hello(conn, frame, &command_str, &command_for_log)
776 .await
777 }
778 "RESET" => {
779 self.shared.hub.remove_connection(conn_key);
782 conn.protocol.set_protocol_version(2);
783 self.write_reply(conn, b"+RESET\r\n".to_vec()).await?;
784 return Ok(Flow::Continue);
785 }
786 "PING" if subscribed_resp2 => {
787 if args.len() > 2 {
788 self.write_reply(conn, encode_error(&wrong_arity("ping")))
789 .await?;
790 } else {
791 let payload = args.get(1).cloned().unwrap_or_default();
792 let reply = Resp::Array(vec![Resp::bulk("pong"), Resp::Bulk(payload)]);
793 self.write_reply(conn, reply.encode(2)).await?;
794 }
795 return Ok(Flow::Continue);
796 }
797 "QUIT" if subscribed_resp2 => {
798 self.write_reply(conn, b"+OK\r\n".to_vec()).await?;
799 let _ = conn.write_half.lock().await.shutdown().await;
800 return Ok(Flow::Close);
801 }
802 "SUBSCRIBE" | "PSUBSCRIBE" | "SSUBSCRIBE" => {
803 let kind = match name.as_str() {
804 "SUBSCRIBE" => Kind::Channel,
805 "PSUBSCRIBE" => Kind::Pattern,
806 _ => Kind::Shard,
807 };
808 return self
809 .subscribe(conn, kind, &name, &args, &command_for_log)
810 .await;
811 }
812 "UNSUBSCRIBE" | "PUNSUBSCRIBE" | "SUNSUBSCRIBE" => {
813 let kind = match name.as_str() {
814 "UNSUBSCRIBE" => Kind::Channel,
815 "PUNSUBSCRIBE" => Kind::Pattern,
816 _ => Kind::Shard,
817 };
818 self.shared
819 .hub
820 .unsubscribe(conn_key, &conn.outbox, proto, kind, &args[1..]);
821 self.write_reply(conn, Vec::new()).await?;
823 return Ok(Flow::Continue);
824 }
825 "PUBLISH" | "SPUBLISH" => {
826 return self.publish(conn, &name, &args, &command_for_log).await;
827 }
828 "PUBSUB" => {
829 let reply = self.pubsub_command(&args, proto);
830 self.write_reply(conn, reply).await?;
831 return Ok(Flow::Continue);
832 }
833 _ => {}
834 }
835
836 if self.shared.cluster.is_some() {
838 if let Some(reply) = self.cluster_command(conn, &name, &args, proto) {
839 self.write_reply(conn, reply).await?;
840 return Ok(Flow::Continue);
841 }
842 }
843
844 let event = Event::new(
845 &REDIS_COMMAND_EVENT,
846 serde_json::json!({
847 "command": command_str,
848 "protocol": proto,
849 }),
850 );
851 let answer = self.ask_model(conn, &event, &command_for_log).await;
852 if answer.accept || answer.deliver {
853 warn!(
854 "Redis connection {}: redis_accept_subscription / redis_deliver_message only \
855 answer redis_subscribe / redis_publish events; ignored for '{}'",
856 self.connection_id, command_for_log
857 );
858 }
859 let mut response = answer.output;
860 if response.is_empty() && !answer.close {
861 warn!(
866 "Redis connection {} decision=fail_closed_no_action command='{}': \
867 no response action produced, replying with an error",
868 self.connection_id, command_for_log
869 );
870 response = encode_error("ERR no response produced for this command");
871 }
872 if !response.is_empty() {
873 self.write_reply(conn, response).await?;
874 }
875 if answer.close {
876 let _ = conn.write_half.lock().await.shutdown().await;
879 return Ok(Flow::Close);
880 }
881 Ok(Flow::Continue)
882 }
883
884 async fn ask_model(
888 &self,
889 conn: &Conn<'_>,
890 event: &Event,
891 command_for_log: &str,
892 ) -> ModelAnswer {
893 let server_id = self
894 .server_id
895 .unwrap_or_else(|| crate::state::ServerId::new(0));
896 let llm_result = call_llm(
897 &self.llm_client,
898 &self.app_state,
899 server_id,
900 Some(self.connection_id),
901 event,
902 conn.protocol.as_ref(),
903 )
904 .await;
905
906 let mut answer = ModelAnswer::default();
907 match llm_result {
908 Ok(execution_result) => {
909 let mut stack: Vec<ActionResult> = execution_result.protocol_results;
913 stack.reverse();
914 while let Some(result) = stack.pop() {
915 match result {
916 ActionResult::Output(bytes) => answer.output.extend_from_slice(&bytes),
917 ActionResult::Multiple(items) => {
918 for inner in items.into_iter().rev() {
919 stack.push(inner);
920 }
921 }
922 ActionResult::CloseConnection => {
923 debug!("Redis closing connection");
924 answer.close = true;
925 }
926 ActionResult::Custom { name, .. } if name == ACCEPT_SUBSCRIPTION => {
927 answer.accept = true;
928 }
929 ActionResult::Custom { name, .. } if name == DELIVER_MESSAGE => {
930 answer.deliver = true;
931 }
932 ActionResult::Custom { name, .. } => {
933 warn!(
936 "Redis: action result '{}' is Custom, not Output; client will receive nothing for it",
937 name
938 );
939 }
940 _ => {
941 }
943 }
944 }
945 }
946 Err(e) => {
947 let overloaded = crate::llm::is_overload_error(&e);
955 let message = redis_error_message(&e, overloaded);
956 let decision = if overloaded {
957 "fail_closed_llm_overloaded"
958 } else {
959 "fail_closed_llm_error"
960 };
961 conn.log.warn(format!(
962 "Redis connection {} decision={} command='{}' replying -{}: {}",
963 self.connection_id, decision, command_for_log, message, e
964 ));
965 answer.output = encode_error(&message);
966 answer.failed = true;
967 }
968 }
969 answer
970 }
971
972 async fn hello(
976 &self,
977 conn: &mut Conn<'_>,
978 frame: &Frame,
979 command_str: &str,
980 command_for_log: &str,
981 ) -> Result<Flow> {
982 let args = hello_args(frame).unwrap_or_default();
983 let mode = if self.shared.cluster.is_some() {
984 "cluster"
985 } else {
986 "standalone"
987 };
988 let id = u64::from(self.connection_id.as_u32());
989 let hello = match resp3::parse_hello(&args) {
990 Err(message) => {
991 self.write_reply(conn, encode_error(&message)).await?;
992 return Ok(Flow::Continue);
993 }
994 Ok(hello) => hello,
995 };
996 let target = hello.protover.unwrap_or(conn.protocol.protocol_version());
997 if hello.auth.is_none() {
998 conn.protocol.set_protocol_version(target);
999 conn.log.info(format!(
1000 "Redis connection {} speaks RESP{} (HELLO)",
1001 self.connection_id, target
1002 ));
1003 self.write_reply(conn, resp3::hello_reply(target, id, mode))
1004 .await?;
1005 return Ok(Flow::Continue);
1006 }
1007 let event = Event::new(
1008 &REDIS_COMMAND_EVENT,
1009 serde_json::json!({
1010 "command": command_str,
1011 "protocol": conn.protocol.protocol_version(),
1012 }),
1013 );
1014 let answer = self.ask_model(conn, &event, command_for_log).await;
1015 let response = if answer.output.starts_with(b"-") {
1016 conn.log.info(format!(
1017 "Redis connection {} decision=model_reject: HELLO with AUTH refused; staying on \
1018 RESP{}",
1019 self.connection_id,
1020 conn.protocol.protocol_version()
1021 ));
1022 answer.output
1023 } else if answer.output.is_empty() && !answer.close {
1024 warn!(
1025 "Redis connection {} decision=fail_closed_no_action command='{}': no answer to \
1026 HELLO with AUTH; refusing",
1027 self.connection_id, command_for_log
1028 );
1029 encode_error("ERR no response produced for this command")
1030 } else {
1031 conn.protocol.set_protocol_version(target);
1032 resp3::hello_reply(target, id, mode)
1033 };
1034 self.write_reply(conn, response).await?;
1035 if answer.close {
1036 let _ = conn.write_half.lock().await.shutdown().await;
1037 return Ok(Flow::Close);
1038 }
1039 Ok(Flow::Continue)
1040 }
1041
1042 async fn subscribe(
1045 &self,
1046 conn: &mut Conn<'_>,
1047 kind: Kind,
1048 name: &str,
1049 args: &[Vec<u8>],
1050 command_for_log: &str,
1051 ) -> Result<Flow> {
1052 let conn_key = self.connection_id.as_u32();
1053 if args.len() < 2 {
1054 self.write_reply(conn, encode_error(&wrong_arity(name)))
1055 .await?;
1056 return Ok(Flow::Continue);
1057 }
1058 let names = &args[1..];
1059 if let Err(message) = self.shared.hub.check_subscribe(conn_key, kind, names) {
1061 conn.log.warn(format!(
1062 "Redis connection {} decision=fail_closed_subscription_bound: {}",
1063 self.connection_id, message
1064 ));
1065 self.write_reply(conn, encode_error(&message)).await?;
1066 return Ok(Flow::Continue);
1067 }
1068 let event = Event::new(
1069 &REDIS_SUBSCRIBE_EVENT,
1070 serde_json::json!({
1071 "command": name,
1072 "channels": names.iter().map(|n| String::from_utf8_lossy(n).to_string()).collect::<Vec<_>>(),
1073 "subscriptions": self.shared.hub.subscription_count(conn_key),
1074 "protocol": conn.protocol.protocol_version(),
1075 }),
1076 );
1077 let answer = self.ask_model(conn, &event, command_for_log).await;
1078 if answer.accept && !answer.failed {
1079 if !answer.output.is_empty() {
1080 warn!(
1081 "Redis connection {}: the subscription was accepted, so the reply verbs \
1082 sent alongside redis_accept_subscription are dropped (the confirmations \
1083 are the reply)",
1084 self.connection_id
1085 );
1086 }
1087 match self.shared.hub.subscribe(
1088 conn_key,
1089 &conn.outbox,
1090 &conn.protocol.protover_cell(),
1091 kind,
1092 names,
1093 ) {
1094 Ok(()) => {
1095 debug!(
1096 "Redis connection {} decision=model_accept command='{}'",
1097 self.connection_id, command_for_log
1098 );
1099 self.write_reply(conn, Vec::new()).await?;
1101 }
1102 Err(message) => {
1103 conn.log.warn(format!(
1104 "Redis connection {} decision=fail_closed_subscription_bound: {}",
1105 self.connection_id, message
1106 ));
1107 self.write_reply(conn, encode_error(&message)).await?;
1108 }
1109 }
1110 } else if !answer.output.is_empty() {
1111 if !answer.failed {
1112 debug!(
1113 "Redis connection {} decision=model_reject command='{}': not subscribed",
1114 self.connection_id, command_for_log
1115 );
1116 }
1117 self.write_reply(conn, answer.output).await?;
1118 } else if !answer.close {
1119 warn!(
1120 "Redis connection {} decision=fail_closed_no_action command='{}': no decision \
1121 on the subscription; refusing it",
1122 self.connection_id, command_for_log
1123 );
1124 self.write_reply(
1125 conn,
1126 encode_error("ERR no response produced for this command"),
1127 )
1128 .await?;
1129 }
1130 if answer.close {
1131 let _ = conn.write_half.lock().await.shutdown().await;
1132 return Ok(Flow::Close);
1133 }
1134 Ok(Flow::Continue)
1135 }
1136
1137 async fn publish(
1140 &self,
1141 conn: &mut Conn<'_>,
1142 name: &str,
1143 args: &[Vec<u8>],
1144 command_for_log: &str,
1145 ) -> Result<Flow> {
1146 if args.len() != 3 {
1147 self.write_reply(conn, encode_error(&wrong_arity(name)))
1148 .await?;
1149 return Ok(Flow::Continue);
1150 }
1151 let shard = name == "SPUBLISH";
1152 let (channel, message) = (&args[1], &args[2]);
1153 let kind = if shard { Kind::Shard } else { Kind::Channel };
1154 if let Err(error) =
1155 pubsub::check_name(kind, channel).and_then(|()| pubsub::check_message(message))
1156 {
1157 conn.log.warn(format!(
1158 "Redis connection {} decision=fail_closed_message_bound: {}",
1159 self.connection_id, error
1160 ));
1161 self.write_reply(conn, encode_error(&error)).await?;
1162 return Ok(Flow::Continue);
1163 }
1164 let event = Event::new(
1165 &REDIS_PUBLISH_EVENT,
1166 serde_json::json!({
1167 "command": name,
1168 "channel": String::from_utf8_lossy(channel),
1169 "message": String::from_utf8_lossy(message),
1170 "receivers": self.shared.hub.receivers(channel, shard),
1171 "protocol": conn.protocol.protocol_version(),
1172 }),
1173 );
1174 let answer = self.ask_model(conn, &event, command_for_log).await;
1175 if answer.deliver && !answer.failed {
1176 if !answer.output.is_empty() {
1177 warn!(
1178 "Redis connection {}: the message was delivered, so the reply verbs sent \
1179 alongside redis_deliver_message are dropped (the receiver count is the \
1180 reply)",
1181 self.connection_id
1182 );
1183 }
1184 let receivers = self.shared.hub.publish(channel, message, shard);
1185 debug!(
1186 "Redis connection {} decision=model_deliver command='{}' receivers={}",
1187 self.connection_id, command_for_log, receivers
1188 );
1189 self.write_reply(conn, encode_integer(receivers as i64))
1190 .await?;
1191 } else if !answer.output.is_empty() {
1192 if !answer.failed {
1193 debug!(
1194 "Redis connection {} decision=model_withhold command='{}': answered without \
1195 delivering",
1196 self.connection_id, command_for_log
1197 );
1198 }
1199 self.write_reply(conn, answer.output).await?;
1200 } else if !answer.close {
1201 warn!(
1202 "Redis connection {} decision=fail_closed_no_action command='{}': no decision \
1203 on the message; nothing delivered",
1204 self.connection_id, command_for_log
1205 );
1206 self.write_reply(
1207 conn,
1208 encode_error("ERR no response produced for this command"),
1209 )
1210 .await?;
1211 }
1212 if answer.close {
1213 let _ = conn.write_half.lock().await.shutdown().await;
1214 return Ok(Flow::Close);
1215 }
1216 Ok(Flow::Continue)
1217 }
1218
1219 fn pubsub_command(&self, args: &[Vec<u8>], proto: u8) -> Vec<u8> {
1221 let sub = args
1222 .get(1)
1223 .map(|a| String::from_utf8_lossy(a).to_ascii_uppercase())
1224 .unwrap_or_default();
1225 let hub = &self.shared.hub;
1226 let channels = |shard: bool| -> Vec<u8> {
1227 if args.len() > 3 {
1228 return encode_error(&wrong_arity(&format!("pubsub|{}", sub.to_lowercase())));
1229 }
1230 let pattern = args.get(2).map(|p| p.as_slice());
1231 if let Some(p) = pattern {
1232 if let Err(e) = pubsub::check_name(Kind::Pattern, p) {
1233 return encode_error(&e);
1234 }
1235 }
1236 Resp::Array(
1237 hub.active_channels(shard, pattern)
1238 .into_iter()
1239 .map(Resp::Bulk)
1240 .collect(),
1241 )
1242 .encode(proto)
1243 };
1244 let numsub = |shard: bool| -> Vec<u8> {
1245 Resp::Map(
1246 hub.numsub(shard, &args[2..])
1247 .into_iter()
1248 .map(|(c, n)| (Resp::Bulk(c), Resp::Int(n as i64)))
1249 .collect(),
1250 )
1251 .encode(proto)
1252 };
1253 match sub.as_str() {
1254 "CHANNELS" => channels(false),
1255 "SHARDCHANNELS" => channels(true),
1256 "NUMSUB" => numsub(false),
1257 "SHARDNUMSUB" => numsub(true),
1258 "NUMPAT" if args.len() == 2 => encode_integer(hub.numpat() as i64),
1259 "NUMPAT" => encode_error(&wrong_arity("pubsub|numpat")),
1260 "" => encode_error(&wrong_arity("pubsub")),
1261 other => encode_error(&format!(
1262 "ERR unknown subcommand '{}'. Try PUBSUB HELP.",
1263 crate::utils::truncate_for_log(&other.to_lowercase(), 64)
1264 )),
1265 }
1266 }
1267
1268 fn cluster_command(
1272 &self,
1273 conn: &mut Conn<'_>,
1274 name: &str,
1275 args: &[Vec<u8>],
1276 proto: u8,
1277 ) -> Option<Vec<u8>> {
1278 match name {
1279 "ASKING" => {
1280 conn.asking = true;
1281 return Some(b"+OK\r\n".to_vec());
1282 }
1283 "READONLY" | "READWRITE" => return Some(b"+OK\r\n".to_vec()),
1284 "CLUSTER" => {}
1285 _ => return None,
1286 }
1287 let sub = args
1288 .get(1)
1289 .map(|a| String::from_utf8_lossy(a).to_ascii_uppercase())
1290 .unwrap_or_default();
1291 let cluster = self.shared.cluster()?;
1292 let reply = match (sub.as_str(), args.len()) {
1293 ("SLOTS", 2) => cluster.slots_reply().encode(proto),
1294 ("SHARDS", 2) => cluster.shards_reply().encode(proto),
1295 ("NODES", 2) => cluster.nodes_reply().encode(proto),
1296 ("INFO", 2) => cluster.info_reply().encode(proto),
1297 ("MYID", 2) => Resp::bulk(&cluster.me().id).encode(proto),
1298 ("KEYSLOT", 3) => encode_integer(i64::from(key_hash_slot(&args[2]))),
1299 ("SLOTS" | "SHARDS" | "NODES" | "INFO" | "MYID" | "KEYSLOT", _) => encode_error(
1300 &wrong_arity(&format!("cluster|{}", sub.to_ascii_lowercase())),
1301 ),
1302 _ => return None,
1303 };
1304 Some(reply)
1305 }
1306}
1307
1308fn frame_args(frame: &Frame) -> Vec<Vec<u8>> {
1310 match frame {
1311 Frame::Array(parts) => parts
1312 .iter()
1313 .map(|f| match f {
1314 Frame::BulkString(b) | Frame::SimpleString(b) => b.clone(),
1315 Frame::Integer(i) => i.to_string().into_bytes(),
1316 other => format!("{other:?}").into_bytes(),
1317 })
1318 .collect(),
1319 other => vec![format!("{other:?}").into_bytes()],
1320 }
1321}
1322
1323fn hello_args(frame: &Frame) -> Option<Vec<String>> {
1325 let Frame::Array(parts) = frame else {
1326 return None;
1327 };
1328 let text = |f: &Frame| match f {
1329 Frame::BulkString(b) | Frame::SimpleString(b) => String::from_utf8_lossy(b).to_string(),
1330 Frame::Integer(i) => i.to_string(),
1331 other => format!("{other:?}"),
1332 };
1333 let first = parts.first().map(text)?;
1334 first
1335 .eq_ignore_ascii_case("HELLO")
1336 .then(|| parts[1..].iter().map(text).collect())
1337}
1338
1339fn frame_to_command_string(frame: &Frame) -> String {
1341 match frame {
1342 Frame::Array(frames) => {
1343 let parts: Vec<String> = frames
1344 .iter()
1345 .map(|f| match f {
1346 Frame::BulkString(bytes) => String::from_utf8_lossy(bytes).to_string(),
1347 Frame::SimpleString(bytes) => String::from_utf8_lossy(bytes).to_string(),
1348 Frame::Integer(i) => i.to_string(),
1349 _ => format!("{:?}", f),
1350 })
1351 .collect();
1352 parts.join(" ")
1353 }
1354 _ => format!("{:?}", frame),
1355 }
1356}
1357
1358fn redis_error_message(err: &anyhow::Error, overloaded: bool) -> String {
1366 let text = crate::utils::WireFailure::classify(err).prefixed_text();
1367 if overloaded {
1368 format!("LOADING {text}")
1369 } else {
1370 format!("ERR {text}")
1371 }
1372}