@@ -157,21 +157,46 @@ static void cmd_quit(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) {
157157 conn -> state = VC_CONN_CLOSING ;
158158}
159159
160+ /* RESET — resets connection state. StackExchange.Redis sends this on reconnect. */
161+ static void cmd_reset (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
162+ (void )srv ; (void )cmd ;
163+ conn -> db_idx = 0 ;
164+ resp_write_simple (WBUF (conn ), "RESET" );
165+ }
166+
160167static void cmd_client (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
161168 (void )srv ;
162169 if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
163170 const char * sub = cmd -> argv [1 ];
164- if (strcasecmp (sub , "SETNAME" ) == 0 ) { OK (conn ); return ; }
165- if (strcasecmp (sub , "GETNAME" ) == 0 ) { BULK (conn , "" , 0 ); return ; }
166- if (strcasecmp (sub , "ID" ) == 0 ) { INT (conn , (int64_t )conn -> fd ); return ; }
171+ if (strcasecmp (sub , "SETNAME" ) == 0 ) { OK (conn ); return ; }
172+ if (strcasecmp (sub , "GETNAME" ) == 0 ) { BULK (conn , "" , 0 ); return ; }
173+ if (strcasecmp (sub , "ID" ) == 0 ) { INT (conn , (int64_t )conn -> fd ); return ; }
174+ /* StackExchange.Redis / RedisInsight no-op stubs */
175+ if (strcasecmp (sub , "NO-EVICT" ) == 0 ) { OK (conn ); return ; }
176+ if (strcasecmp (sub , "NO-TOUCH" ) == 0 ) { OK (conn ); return ; }
177+ if (strcasecmp (sub , "UNPAUSE" ) == 0 ) { OK (conn ); return ; }
178+ if (strcasecmp (sub , "PAUSE" ) == 0 ) { OK (conn ); return ; }
179+ if (strcasecmp (sub , "CACHING" ) == 0 ) { OK (conn ); return ; }
180+ if (strcasecmp (sub , "REPLY" ) == 0 ) { OK (conn ); return ; }
181+ if (strcasecmp (sub , "TRACKING" ) == 0 ) { OK (conn ); return ; }
167182 if (strcasecmp (sub , "INFO" ) == 0 ) {
168183 char info [256 ];
169184 snprintf (info , sizeof (info ),
170185 "id=%d\r\naddr=%s:%u\r\ncmd=client\r\n" ,
171186 conn -> fd , conn -> peer_addr , conn -> peer_port );
172187 BULK (conn , info , strlen (info )); return ;
173188 }
174- ERR (conn , "ERR" , "unknown CLIENT subcommand" );
189+ if (strcasecmp (sub , "LIST" ) == 0 ) {
190+ /* Return minimal CLIENT LIST entry for this connection */
191+ char buf [512 ];
192+ int n = snprintf (buf , sizeof (buf ),
193+ "id=%d addr=%s:%u fd=%d name= age=0 idle=0 flags=N "
194+ "db=0 sub=0 psub=0 multi=-1 rbs=16384 rbp=0 "
195+ "obl=0 oll=0 omem=0 events=r cmd=client resp=2\n" ,
196+ conn -> fd , conn -> peer_addr , conn -> peer_port , conn -> fd );
197+ BULK (conn , buf , (size_t )n ); return ;
198+ }
199+ resp_write_error (WBUF (conn ), "ERR" , "unknown CLIENT subcommand" );
175200}
176201
177202/* ══════════════════════════════════════════════════════════════
@@ -583,6 +608,8 @@ static void cmd_info(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) {
583608 char info [4096 ];
584609 int n = snprintf (info , sizeof (info ),
585610 "# Server\r\n"
611+ "redis_version:%s\r\n"
612+ "redis_mode:%s\r\n"
586613 "server:voidcache\r\n"
587614 "version:%s\r\n"
588615 "node_id:%s\r\n"
@@ -604,10 +631,29 @@ static void cmd_info(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) {
604631 "# Keyspace\r\n"
605632 "db0:keys=%llu\r\n"
606633 "\r\n"
634+ "# Replication\r\n"
635+ "role:master\r\n"
636+ "connected_slaves:0\r\n"
637+ "master_failover_state:no-failover\r\n"
638+ "master_replid:0000000000000000000000000000000000000000\r\n"
639+ "master_repl_offset:0\r\n"
640+ "\r\n"
641+ "# Persistence\r\n"
642+ "loading:0\r\n"
643+ "rdb_changes_since_last_save:0\r\n"
644+ "rdb_last_bgsave_status:ok\r\n"
645+ "aof_enabled:0\r\n"
646+ "\r\n"
647+ "# CPU\r\n"
648+ "used_cpu_sys:0\r\n"
649+ "used_cpu_user:0\r\n"
650+ "\r\n"
607651 "# Memory\r\n"
608652 "used_memory:%llu\r\n"
609653 "maxmemory:%llu\r\n" ,
610654 VC_SERVER_VERSION ,
655+ srv -> cfg .cluster_enabled ? "cluster" : "standalone" ,
656+ VC_SERVER_VERSION ,
611657 srv -> node_id ,
612658 srv -> cfg .cluster_enabled ? "cluster" : "standalone" ,
613659 (unsigned long long )atomic_load (& srv -> total_conns ),
@@ -629,8 +675,44 @@ static void cmd_config(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) {
629675 (void )srv ;
630676 if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
631677 if (strcasecmp (cmd -> argv [1 ], "GET" ) == 0 && cmd -> argc >= 3 ) {
632- /* Return empty array — no config params exposed */
633- resp_write_array_header (WBUF (conn ), 0 );
678+ /* Return a minimal but realistic config for driver compatibility.
679+ * StackExchange.Redis and RedisInsight query these on connect. */
680+ const char * pat = cmd -> argv [2 ];
681+ /* Helper: does the glob pattern match the key? (simple * wildcard) */
682+ #define CFGMATCH (k ) (strcmp(pat,"*")==0 || strcasecmp(pat,(k))==0 || (pat[strlen(pat)-1]=='*' && strncasecmp(pat,(k),strlen(pat)-1)==0))
683+ char maxmem [32 ];
684+ snprintf (maxmem , sizeof (maxmem ), "%llu" ,
685+ (unsigned long long )srv -> cache -> max_memory );
686+ /* Build array of matched key-value pairs */
687+ struct { const char * k ; const char * v ; } entries [] = {
688+ { "hz" , "10" },
689+ { "maxmemory" , maxmem },
690+ { "maxmemory-policy" , "allkeys-lru" },
691+ { "save" , "" },
692+ { "appendonly" , "no" },
693+ { "list-max-ziplist-size" , "-2" },
694+ { "list-compress-depth" , "0" },
695+ { "activerehashing" , "yes" },
696+ { "lazyfree-lazy-eviction" , "no" },
697+ { "latency-tracking" , "yes" },
698+ { "latency-tracking-info-percentiles" , "50 99 99.9" },
699+ { "proto-max-bulk-len" , "536870912" },
700+ { "activedefrag" , "no" },
701+ { "repl-backlog-size" , "1048576" },
702+ { "bind-source-addr" , "" },
703+ { "socket-mark-id" , "0" },
704+ };
705+ int nmatch = 0 ;
706+ for (size_t e = 0 ; e < sizeof (entries )/sizeof (entries [0 ]); e ++ )
707+ if (CFGMATCH (entries [e ].k )) nmatch ++ ;
708+ resp_write_array_header (WBUF (conn ), nmatch * 2 );
709+ for (size_t e = 0 ; e < sizeof (entries )/sizeof (entries [0 ]); e ++ ) {
710+ if (CFGMATCH (entries [e ].k )) {
711+ BULK (conn , entries [e ].k , strlen (entries [e ].k ));
712+ BULK (conn , entries [e ].v , strlen (entries [e ].v ));
713+ }
714+ }
715+ #undef CFGMATCH
634716 return ;
635717 }
636718 if (strcasecmp (cmd -> argv [1 ], "SET" ) == 0 ) { OK (conn ); return ; }
@@ -885,6 +967,91 @@ typedef struct {
885967 uint32_t acl_needed ; /* 0 = always allowed (PING/AUTH/HELLO) */
886968} vc_cmd_entry_t ;
887969
970+ /* ── ACL commands (RedisInsight + driver feature detection) ────────────── */
971+ static void cmd_acl (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
972+ (void )srv ;
973+ if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
974+ const char * sub = cmd -> argv [1 ];
975+ if (strcasecmp (sub , "WHOAMI" ) == 0 ) {
976+ const char * name = (conn -> user && conn -> user -> username [0 ])
977+ ? conn -> user -> username : "default" ;
978+ BULK (conn , name , strlen (name )); return ;
979+ }
980+ if (strcasecmp (sub , "CAT" ) == 0 ) {
981+ resp_write_array_header (WBUF (conn ), 4 );
982+ BULK (conn , "read" , 4 ); BULK (conn , "write" , 5 );
983+ BULK (conn , "admin" , 5 ); BULK (conn , "pubsub" , 6 ); return ;
984+ }
985+ if (strcasecmp (sub , "LIST" ) == 0 ) {
986+ resp_write_array_header (WBUF (conn ), 1 );
987+ const char * name = (conn -> user && conn -> user -> username [0 ])
988+ ? conn -> user -> username : "default" ;
989+ char rule [128 ];
990+ snprintf (rule , sizeof (rule ), "user %s on ~* &* +@all" , name );
991+ BULK (conn , rule , strlen (rule )); return ;
992+ }
993+ if (strcasecmp (sub , "GETUSER" ) == 0 ) { resp_write_null_compat (WBUF (conn ), conn -> resp3 ); return ; }
994+ if (strcasecmp (sub , "SETUSER" ) == 0 ||
995+ strcasecmp (sub , "DELUSER" ) == 0 ||
996+ strcasecmp (sub , "LOG" ) == 0 ||
997+ strcasecmp (sub , "RESET" ) == 0 ) { OK (conn ); return ; }
998+ resp_write_error (WBUF (conn ), "ERR" , "unknown ACL subcommand" );
999+ }
1000+
1001+ /* ── LATENCY (RedisInsight monitoring) ─────────────────────────────────── */
1002+ static void cmd_latency (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1003+ (void )srv ;
1004+ if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
1005+ const char * sub = cmd -> argv [1 ];
1006+ if (strcasecmp (sub , "LATEST" ) == 0 ) { resp_write_array_header (WBUF (conn ), 0 ); return ; }
1007+ if (strcasecmp (sub , "HISTORY" ) == 0 ) { resp_write_array_header (WBUF (conn ), 0 ); return ; }
1008+ if (strcasecmp (sub , "RESET" ) == 0 ) { INT (conn , 0 ); return ; }
1009+ if (strcasecmp (sub , "GRAPH" ) == 0 ) { BULK (conn , "" , 0 ); return ; }
1010+ resp_write_error (WBUF (conn ), "ERR" , "unknown LATENCY subcommand" );
1011+ }
1012+
1013+ /* ── SLOWLOG (RedisInsight) ─────────────────────────────────────────────── */
1014+ static void cmd_slowlog (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1015+ (void )srv ;
1016+ if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
1017+ const char * sub = cmd -> argv [1 ];
1018+ if (strcasecmp (sub , "GET" ) == 0 ) { resp_write_array_header (WBUF (conn ), 0 ); return ; }
1019+ if (strcasecmp (sub , "LEN" ) == 0 ) { INT (conn , 0 ); return ; }
1020+ if (strcasecmp (sub , "RESET" ) == 0 ) { OK (conn ); return ; }
1021+ resp_write_error (WBUF (conn ), "ERR" , "unknown SLOWLOG subcommand" );
1022+ }
1023+
1024+ /* ── OBJECT (RedisInsight key inspector) ────────────────────────────────── */
1025+ static void cmd_object (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1026+ (void )srv ;
1027+ if (cmd -> argc < 2 ) { ERR (conn , "ERR" , "wrong arguments" ); return ; }
1028+ const char * sub = cmd -> argv [1 ];
1029+ if (strcasecmp (sub , "ENCODING" ) == 0 ) { BULK (conn , "embstr" , 7 ); return ; }
1030+ if (strcasecmp (sub , "REFCOUNT" ) == 0 ) { INT (conn , 1 ); return ; }
1031+ if (strcasecmp (sub , "IDLETIME" ) == 0 ) { INT (conn , 0 ); return ; }
1032+ if (strcasecmp (sub , "FREQ" ) == 0 ) { INT (conn , 0 ); return ; }
1033+ if (strcasecmp (sub , "HELP" ) == 0 ) { resp_write_array_header (WBUF (conn ), 0 ); return ; }
1034+ resp_write_error (WBUF (conn ), "ERR" , "unknown OBJECT subcommand" );
1035+ }
1036+
1037+ /* ── WAIT (SE.Redis replication sync) ──────────────────────────────────── */
1038+ static void cmd_wait (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1039+ (void )srv ; (void )cmd ;
1040+ INT (conn , 0 ); /* single-node: 0 replicas */
1041+ }
1042+
1043+ /* ── XLEN stub (Streams feature detection) ──────────────────────────────── */
1044+ static void cmd_xlen (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1045+ (void )srv ; (void )cmd ;
1046+ ERR (conn , "WRONGTYPE" , "Operation against a key holding the wrong kind of value" );
1047+ }
1048+
1049+ /* ── SRANDMEMBER stub ───────────────────────────────────────────────────── */
1050+ static void cmd_srandmember (vcserver_t * srv , vc_conn_t * conn , vc_cmd_t * cmd ) {
1051+ (void )srv ; (void )cmd ;
1052+ ERR (conn , "WRONGTYPE" , "Operation against a key holding the wrong kind of value" );
1053+ }
1054+
8881055static const vc_cmd_entry_t cmd_table [] = {
8891056 /* Connection */
8901057 { "PING" , cmd_ping , 0 },
@@ -928,6 +1095,15 @@ static const vc_cmd_entry_t cmd_table[] = {
9281095 { "VCSET" , cmd_vcset , VC_ACL_WRITE },
9291096 { "VCGET" , cmd_vcget , VC_ACL_READ },
9301097 { "VCINFO" , cmd_vcinfo , VC_ACL_READ },
1098+ /* StackExchange.Redis + RedisInsight compatibility */
1099+ { "RESET" , cmd_reset , 0 },
1100+ { "ACL" , cmd_acl , 0 },
1101+ { "LATENCY" , cmd_latency , VC_ACL_ADMIN },
1102+ { "SLOWLOG" , cmd_slowlog , VC_ACL_ADMIN },
1103+ { "OBJECT" , cmd_object , VC_ACL_READ },
1104+ { "WAIT" , cmd_wait , 0 },
1105+ { "XLEN" , cmd_xlen , VC_ACL_READ },
1106+ { "SRANDMEMBER" , cmd_srandmember , VC_ACL_READ },
9311107};
9321108
9331109#define CMD_TABLE_SIZE (sizeof(cmd_table) / sizeof(cmd_table[0]))
0 commit comments