Touchup TODOs in the Redis analyzer

Also renames `KnownCommand` to `RedisCommand` to avoid conflicts.
This commit is contained in:
Evan Typanski 2025-06-26 14:08:28 -04:00
parent 64443e5e5a
commit e7c798e526
6 changed files with 131 additions and 140 deletions

View file

@ -74,17 +74,17 @@ export {
option max_pending_commands = 10000;
# These commands enter subscribed mode
global enter_subscribed_mode = [KnownCommand_PSUBSCRIBE,
KnownCommand_SSUBSCRIBE, KnownCommand_SUBSCRIBE];
global enter_subscribed_mode = [RedisCommand_PSUBSCRIBE,
RedisCommand_SSUBSCRIBE, RedisCommand_SUBSCRIBE];
# These commands exit subscribed mode
global exit_subscribed_mode = [KnownCommand_RESET, KnownCommand_QUIT];
global exit_subscribed_mode = [RedisCommand_RESET, RedisCommand_QUIT];
# These commands don't expect a response (ever) - their replies are out of band.
global no_response_commands = [KnownCommand_PSUBSCRIBE,
KnownCommand_PUNSUBSCRIBE, KnownCommand_SSUBSCRIBE,
KnownCommand_SUBSCRIBE, KnownCommand_SUNSUBSCRIBE,
KnownCommand_UNSUBSCRIBE];
global no_response_commands = [RedisCommand_PSUBSCRIBE,
RedisCommand_PUNSUBSCRIBE, RedisCommand_SSUBSCRIBE,
RedisCommand_SUBSCRIBE, RedisCommand_SUNSUBSCRIBE,
RedisCommand_UNSUBSCRIBE];
}
redef record connection += {
@ -147,7 +147,7 @@ function is_last_interval_closed(c: connection): bool
c$redis_state$no_reply_ranges[-1]?$end;
}
event Redis::hello_command(c: connection, hello: HelloCommand)
event hello_command(c: connection, hello: HelloCommand)
{
if ( ! c?$redis_state )
make_new_state(c);
@ -156,7 +156,7 @@ event Redis::hello_command(c: connection, hello: HelloCommand)
c$redis_state$resp_version = RESP3;
}
event Redis::command(c: connection, cmd: Command)
event command(c: connection, cmd: Command)
{
if ( ! c?$redis_state )
make_new_state(c);
@ -196,7 +196,7 @@ event Redis::command(c: connection, cmd: Command)
# CLIENT commands can skip a number of replies and may be used with
# pipelining. We need special logic in order to track the command/reply
# pairs.
if ( cmd?$known && cmd$known == KnownCommand_CLIENT )
if ( cmd?$known && cmd$known == RedisCommand_CLIENT )
{
# All 3 CLIENT commands we care about have 3 elements
if ( |cmd$raw| == 3 )
@ -291,11 +291,12 @@ function log_from(c: connection, previous_reply_num: count)
}
}
event Redis::reply(c: connection, data: ReplyData)
event reply(c: connection, data: ReplyData)
{
if ( ! c?$redis_state )
make_new_state(c);
# If the server is talking in RESP3, mark accordingly, even if we didn't see HELLO
if ( data$min_protocol_version == 3 )
{
c$redis_state$resp_version = RESP3;
@ -307,6 +308,7 @@ event Redis::reply(c: connection, data: ReplyData)
event server_push(c, data);
return;
}
local previous_reply_num = c$redis_state$current_reply;
c$redis_state$current_reply = reply_num(c);
set_state(c, F);
@ -320,7 +322,7 @@ event Redis::reply(c: connection, data: ReplyData)
clear_table(c$redis_state$skip_commands);
}
event Redis::error(c: connection, data: ReplyData)
event error(c: connection, data: ReplyData)
{
if ( ! c?$redis_state )
make_new_state(c);

View file

@ -55,7 +55,7 @@ export {
## The value, if this command is known to have a value
value: string &log &optional;
## The command in an enum if it was known
known: KnownCommand &optional;
known: RedisCommand &optional;
};
## A generic Redis reply from the client.

View file

@ -6,7 +6,7 @@ module Redis;
import RESP;
public type KnownCommand = enum {
public type RedisCommand = enum {
APPEND,
AUTH,
BITCOUNT,
@ -64,7 +64,7 @@ type Command = struct {
name: bytes;
key: optional<bytes>;
value: optional<bytes>;
known: optional<KnownCommand>;
known: optional<RedisCommand>;
};
# This just assumes all elements in the array is a bulk string and puts them in a vector
@ -158,44 +158,44 @@ function parse_command(raw: vector<bytes>): Command {
if (|raw| >= 2) {
switch (*cmd) {
case KnownCommand::KEYS:
case RedisCommand::KEYS:
parsed.key = raw[1];
case KnownCommand::APPEND,
KnownCommand::BITCOUNT,
KnownCommand::BITFIELD,
KnownCommand::BITFIELD_RO,
KnownCommand::BITPOS,
KnownCommand::BLPOP,
KnownCommand::BRPOP,
KnownCommand::COPY,
KnownCommand::DECR,
KnownCommand::DECRBY,
KnownCommand::DEL,
KnownCommand::DUMP,
KnownCommand::EXISTS,
KnownCommand::EXPIRE,
KnownCommand::EXPIREAT,
KnownCommand::EXPIRETIME,
KnownCommand::GET,
KnownCommand::GETBIT,
KnownCommand::GETDEL,
KnownCommand::GETEX,
KnownCommand::GETRANGE,
KnownCommand::GETSET,
KnownCommand::HDEL,
KnownCommand::HGET,
KnownCommand::HSET,
KnownCommand::INCR,
KnownCommand::INCRBY,
KnownCommand::MGET,
KnownCommand::MOVE,
KnownCommand::MSET,
KnownCommand::PERSIST,
KnownCommand::RENAME,
KnownCommand::SET,
KnownCommand::STRLEN,
KnownCommand::TTL,
KnownCommand::TYPE:
case RedisCommand::APPEND,
RedisCommand::BITCOUNT,
RedisCommand::BITFIELD,
RedisCommand::BITFIELD_RO,
RedisCommand::BITPOS,
RedisCommand::BLPOP,
RedisCommand::BRPOP,
RedisCommand::COPY,
RedisCommand::DECR,
RedisCommand::DECRBY,
RedisCommand::DEL,
RedisCommand::DUMP,
RedisCommand::EXISTS,
RedisCommand::EXPIRE,
RedisCommand::EXPIREAT,
RedisCommand::EXPIRETIME,
RedisCommand::GET,
RedisCommand::GETBIT,
RedisCommand::GETDEL,
RedisCommand::GETEX,
RedisCommand::GETRANGE,
RedisCommand::GETSET,
RedisCommand::HDEL,
RedisCommand::HGET,
RedisCommand::HSET,
RedisCommand::INCR,
RedisCommand::INCRBY,
RedisCommand::MGET,
RedisCommand::MOVE,
RedisCommand::MSET,
RedisCommand::PERSIST,
RedisCommand::RENAME,
RedisCommand::SET,
RedisCommand::STRLEN,
RedisCommand::TTL,
RedisCommand::TYPE:
parsed.key = raw[1];
default: ();
}
@ -203,22 +203,22 @@ function parse_command(raw: vector<bytes>): Command {
if (|raw| >= 3) {
switch (*cmd) {
case KnownCommand::SET,
KnownCommand::APPEND,
KnownCommand::DECRBY,
KnownCommand::EXPIRE,
KnownCommand::EXPIREAT,
KnownCommand::GETBIT,
KnownCommand::GETSET,
KnownCommand::HDEL,
KnownCommand::HGET,
KnownCommand::INCRBY,
KnownCommand::MOVE,
KnownCommand::MSET,
KnownCommand::RENAME:
case RedisCommand::SET,
RedisCommand::APPEND,
RedisCommand::DECRBY,
RedisCommand::EXPIRE,
RedisCommand::EXPIREAT,
RedisCommand::GETBIT,
RedisCommand::GETSET,
RedisCommand::HDEL,
RedisCommand::HGET,
RedisCommand::INCRBY,
RedisCommand::MOVE,
RedisCommand::MSET,
RedisCommand::RENAME:
parsed.value = raw[2];
# Op first, destination second, then a list of keys. Just log dest
case KnownCommand::BITOP: parsed.key = raw[2];
case RedisCommand::BITOP: parsed.key = raw[2];
default: ();
}
}
@ -226,7 +226,7 @@ function parse_command(raw: vector<bytes>): Command {
if (|raw| >= 4) {
switch (*cmd) {
# timeout, numkeys, then key
case KnownCommand::BLMPOP: parsed.key = raw[3];
case RedisCommand::BLMPOP: parsed.key = raw[3];
default: ();
}
}
@ -234,60 +234,60 @@ function parse_command(raw: vector<bytes>): Command {
return parsed;
}
function command_from(cmd_bytes: bytes): optional<KnownCommand> {
local cmd: optional<KnownCommand> = Null;
function command_from(cmd_bytes: bytes): optional<RedisCommand> {
local cmd: optional<RedisCommand> = Null;
switch (cmd_bytes.lower()) {
case b"append": cmd = KnownCommand::APPEND;
case b"auth": cmd = KnownCommand::AUTH;
case b"bitcount": cmd = KnownCommand::BITCOUNT;
case b"bitfield": cmd = KnownCommand::BITFIELD;
case b"bitfield_ro": cmd = KnownCommand::BITFIELD_RO;
case b"bitop": cmd = KnownCommand::BITOP;
case b"bitpos": cmd = KnownCommand::BITPOS;
case b"blmpop": cmd = KnownCommand::BLMPOP;
case b"blpop": cmd = KnownCommand::BLPOP;
case b"brpop": cmd = KnownCommand::BRPOP;
case b"client": cmd = KnownCommand::CLIENT;
case b"copy": cmd = KnownCommand::COPY;
case b"decr": cmd = KnownCommand::DECR;
case b"decrby": cmd = KnownCommand::DECRBY;
case b"del": cmd = KnownCommand::DEL;
case b"dump": cmd = KnownCommand::DUMP;
case b"exists": cmd = KnownCommand::EXISTS;
case b"expire": cmd = KnownCommand::EXPIRE;
case b"expireat": cmd = KnownCommand::EXPIREAT;
case b"expiretime": cmd = KnownCommand::EXPIRETIME;
case b"expiretime": cmd = KnownCommand::EXPIRETIME;
case b"get": cmd = KnownCommand::GET;
case b"getbit": cmd = KnownCommand::GETBIT;
case b"getdel": cmd = KnownCommand::GETDEL;
case b"getex": cmd = KnownCommand::GETEX;
case b"getrange": cmd = KnownCommand::GETRANGE;
case b"getset": cmd = KnownCommand::GETSET;
case b"hdel": cmd = KnownCommand::HDEL;
case b"hello": cmd = KnownCommand::HELLO;
case b"hget": cmd = KnownCommand::HGET;
case b"hset": cmd = KnownCommand::HSET;
case b"incr": cmd = KnownCommand::INCR;
case b"incrby": cmd = KnownCommand::INCRBY;
case b"keys": cmd = KnownCommand::KEYS;
case b"mget": cmd = KnownCommand::MGET;
case b"move": cmd = KnownCommand::MOVE;
case b"mset": cmd = KnownCommand::MSET;
case b"persist": cmd = KnownCommand::PERSIST;
case b"psubscribe": cmd = KnownCommand::PSUBSCRIBE;
case b"punsubscribe": cmd = KnownCommand::PUNSUBSCRIBE;
case b"quit": cmd = KnownCommand::QUIT;
case b"rename": cmd = KnownCommand::RENAME;
case b"reset": cmd = KnownCommand::RESET;
case b"set": cmd = KnownCommand::SET;
case b"strlen": cmd = KnownCommand::STRLEN;
case b"ssubscribe": cmd = KnownCommand::SSUBSCRIBE;
case b"subscribe": cmd = KnownCommand::SUBSCRIBE;
case b"sunsubscribe": cmd = KnownCommand::SUNSUBSCRIBE;
case b"ttl": cmd = KnownCommand::TTL;
case b"type": cmd = KnownCommand::TYPE;
case b"unsubscribe": cmd = KnownCommand::UNSUBSCRIBE;
case b"append": cmd = RedisCommand::APPEND;
case b"auth": cmd = RedisCommand::AUTH;
case b"bitcount": cmd = RedisCommand::BITCOUNT;
case b"bitfield": cmd = RedisCommand::BITFIELD;
case b"bitfield_ro": cmd = RedisCommand::BITFIELD_RO;
case b"bitop": cmd = RedisCommand::BITOP;
case b"bitpos": cmd = RedisCommand::BITPOS;
case b"blmpop": cmd = RedisCommand::BLMPOP;
case b"blpop": cmd = RedisCommand::BLPOP;
case b"brpop": cmd = RedisCommand::BRPOP;
case b"client": cmd = RedisCommand::CLIENT;
case b"copy": cmd = RedisCommand::COPY;
case b"decr": cmd = RedisCommand::DECR;
case b"decrby": cmd = RedisCommand::DECRBY;
case b"del": cmd = RedisCommand::DEL;
case b"dump": cmd = RedisCommand::DUMP;
case b"exists": cmd = RedisCommand::EXISTS;
case b"expire": cmd = RedisCommand::EXPIRE;
case b"expireat": cmd = RedisCommand::EXPIREAT;
case b"expiretime": cmd = RedisCommand::EXPIRETIME;
case b"expiretime": cmd = RedisCommand::EXPIRETIME;
case b"get": cmd = RedisCommand::GET;
case b"getbit": cmd = RedisCommand::GETBIT;
case b"getdel": cmd = RedisCommand::GETDEL;
case b"getex": cmd = RedisCommand::GETEX;
case b"getrange": cmd = RedisCommand::GETRANGE;
case b"getset": cmd = RedisCommand::GETSET;
case b"hdel": cmd = RedisCommand::HDEL;
case b"hello": cmd = RedisCommand::HELLO;
case b"hget": cmd = RedisCommand::HGET;
case b"hset": cmd = RedisCommand::HSET;
case b"incr": cmd = RedisCommand::INCR;
case b"incrby": cmd = RedisCommand::INCRBY;
case b"keys": cmd = RedisCommand::KEYS;
case b"mget": cmd = RedisCommand::MGET;
case b"move": cmd = RedisCommand::MOVE;
case b"mset": cmd = RedisCommand::MSET;
case b"persist": cmd = RedisCommand::PERSIST;
case b"psubscribe": cmd = RedisCommand::PSUBSCRIBE;
case b"punsubscribe": cmd = RedisCommand::PUNSUBSCRIBE;
case b"quit": cmd = RedisCommand::QUIT;
case b"rename": cmd = RedisCommand::RENAME;
case b"reset": cmd = RedisCommand::RESET;
case b"set": cmd = RedisCommand::SET;
case b"strlen": cmd = RedisCommand::STRLEN;
case b"ssubscribe": cmd = RedisCommand::SSUBSCRIBE;
case b"subscribe": cmd = RedisCommand::SUBSCRIBE;
case b"sunsubscribe": cmd = RedisCommand::SUNSUBSCRIBE;
case b"ttl": cmd = RedisCommand::TTL;
case b"type": cmd = RedisCommand::TYPE;
case b"unsubscribe": cmd = RedisCommand::UNSUBSCRIBE;
default: cmd = Null;
}
@ -352,7 +352,7 @@ public function make_set(command: Command): Set {
}
public function is_set(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::SET && data.command.key && data.command.value;
return data.command.known && *(data.command.known) == RedisCommand::SET && data.command.key && data.command.value;
}
type Get = struct {
@ -365,7 +365,7 @@ public function make_get(command: Command): Get {
}
public function is_get(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::GET && |data.command.raw| >= 2;
return data.command.known && *(data.command.known) == RedisCommand::GET && |data.command.raw| >= 2;
}
type Auth = struct {
@ -383,7 +383,7 @@ public function make_auth(command: Command): Auth {
}
public function is_auth(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::AUTH && |data.command.raw| >= 2;
return data.command.known && *(data.command.known) == RedisCommand::AUTH && |data.command.raw| >= 2;
}
type Hello = struct {
@ -398,7 +398,7 @@ public function make_hello(command: Command): Hello {
}
public function is_hello(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::HELLO;
return data.command.known && *(data.command.known) == RedisCommand::HELLO;
}
type ReplyData = struct {
@ -431,7 +431,7 @@ function bulk_string_content(bulk: RESP::BulkString): bytes {
return b"";
}
function stringify_map(data: RESP::Map): bytes {
function stringify_map(data: RESP::Map&): bytes {
local res = b"{";
local first = True;
local i = 0;
@ -451,7 +451,7 @@ function stringify_map(data: RESP::Map): bytes {
}
# Returns the bytes string value of this, or Null if it cannot.
function stringify(data: RESP::Data): bytes {
function stringify(data: RESP::Data&): bytes {
if (data?.simple_string)
return data.simple_string.content;
else if (data?.simple_error)

View file

@ -7,7 +7,7 @@ protocol analyzer Redis over TCP:
import RESP;
import Redis;
export Redis::KnownCommand;
export Redis::RedisCommand;
on RESP::ClientData if ( Redis::is_set(self) ) -> event Redis::set_command($conn, Redis::make_set(self.command));
on RESP::ClientData if ( Redis::is_get(self) ) -> event Redis::get_command($conn, Redis::make_get(self.command).key);

View file

@ -116,7 +116,7 @@ type Data = unit(depth: uint8&) {
DataType::MAP -> map_: Map(depth);
DataType::SET -> set_: Set(depth);
# "Push events are encoded similarly to arrays, differing only in their
# first byte" - TODO: can probably make it more obvious, though
# first byte"
DataType::PUSH -> push: Array(depth);
};
@ -197,22 +197,11 @@ type BigNum = unit {
type Map = unit(depth: uint8&) {
num_elements: RedisBytes &convert=$$.to_uint(10);
# TODO: How can I make this into a map? Alternatively, how can I do this better?
raw_data: Data(depth)[self.num_elements * 2];
# TODO: This is broken. See https://github.com/zeek/spicy/issues/2061
# var key_val_pairs: vector<tuple<Data, Data>>;
# on raw_data {
# while (local i = 0; i < self.num_elements) {
# self.key_val_pairs.push_back(($$[i], $$[i + 1]));
# i += 2;
# }
# }
};
type Set = unit(depth: uint8&) {
num_elements: RedisBytes &convert=$$.to_uint(10) &requires=self.num_elements <= MAX_SIZE;
# TODO: This should be a set but doesn't go in the backed C++ set
elements: Data(depth)[self.num_elements];
};

View file

@ -589,7 +589,7 @@ connection {
* cmd: record Redis::Command, log=T, optional=F
Redis::Command {
* key: string, log=T, optional=T
* known: enum Redis::KnownCommand, log=F, optional=T
* known: enum Redis::RedisCommand, log=F, optional=T
* name: string, log=T, optional=F
* raw: vector of string, log=F, optional=F
* value: string, log=T, optional=T