spicy-redis: Add some commands and touch up parsing

This commit is contained in:
Evan Typanski 2024-11-06 13:43:44 -05:00
parent 22bda56af3
commit f0e9f46c7c
21 changed files with 200 additions and 114 deletions

View file

@ -26,6 +26,11 @@ export {
key: string &log; key: string &log;
}; };
type AuthCommand: record {
username: string &optional;
password: string;
};
type Command: record { type Command: record {
## The raw command, exactly as parsed ## The raw command, exactly as parsed
raw: vector of string; raw: vector of string;
@ -71,15 +76,18 @@ export {
type State: record { type State: record {
## Pending requests. ## Pending requests.
pending: table[count] of Info; pending: table[count] of Info;
## Current request in the pending queue. ## Current request in the pending queue.
current_request: count &default=0; current_request: count &default=0;
## Current response in the pending queue. ## Current response in the pending queue.
current_response: count &default=0; current_response: count &default=0;
## Ranges where we do not expect a response ## Ranges where we do not expect a response
## Each range is one or two elements, one meaning it's unbounded, two meaning ## Each range is one or two elements, one meaning it's unbounded, two meaning
## it begins at one and ends at the second. ## it begins at one and ends at the second.
no_response_ranges: vector of vector of count; no_response_ranges: vector of vector of count;
## We store if this analyzer had a violation to avoid logging if so.
## This should not be super necessary, but worth a shot.
violation: bool &default=F;
}; };
# Redis specifically mentions 10k commands as a good pipelining threshold, so # Redis specifically mentions 10k commands as a good pipelining threshold, so
@ -102,6 +110,21 @@ event zeek_init() &priority=5
Analyzer::register_for_ports(Analyzer::ANALYZER_SPICY_REDIS, ports); Analyzer::register_for_ports(Analyzer::ANALYZER_SPICY_REDIS, ports);
} }
event analyzer_violation_info(atype: AllAnalyzers::Tag,
info: AnalyzerViolationInfo)
{
if ( atype == Analyzer::ANALYZER_SPICY_REDIS )
{
if ( info?$c )
{
if ( info$c?$redis_state )
{
info$c$redis_state$violation = T;
}
}
}
}
function new_redis_session(c: connection): Info function new_redis_session(c: connection): Info
{ {
return Info($ts=network_time(), $uid=c$uid, $id=c$id); return Info($ts=network_time(), $uid=c$uid, $id=c$id);
@ -116,11 +139,14 @@ function make_new_state(c: connection)
function set_state(c: connection, is_orig: bool) function set_state(c: connection, is_orig: bool)
{ {
if ( ! c?$redis_state ) make_new_state(c); if ( ! c?$redis_state )
make_new_state(c);
local current: count; local current: count;
if ( is_orig ) current = c$redis_state$current_request; if ( is_orig )
else current = c$redis_state$current_response; current = c$redis_state$current_request;
else
current = c$redis_state$current_response;
if ( current !in c$redis_state$pending ) if ( current !in c$redis_state$pending )
c$redis_state$pending[current] = new_redis_session(c); c$redis_state$pending[current] = new_redis_session(c);
@ -131,28 +157,24 @@ function set_state(c: connection, is_orig: bool)
# Returns true if the last interval exists and is closed # Returns true if the last interval exists and is closed
function is_last_interval_closed(c: connection): bool function is_last_interval_closed(c: connection): bool
{ {
return |c$redis_state$no_response_ranges| == 0 || |c$redis_state$no_response_ranges[|c$redis_state$no_response_ranges| - 1]| != 1; return |c$redis_state$no_response_ranges| == 0
|| |c$redis_state$no_response_ranges[|c$redis_state$no_response_ranges| - 1]| != 1;
} }
event Redis::command(c: connection, is_orig: bool, command: Command) event Redis::command(c: connection, is_orig: bool, command: Command)
{ {
if ( ! c?$redis_state ) make_new_state(c); if ( ! c?$redis_state )
make_new_state(c);
if ( max_pending_requests > 0 && |c$redis_state$pending| > max_pending_requests ) if ( max_pending_requests > 0
&& |c$redis_state$pending| > max_pending_requests )
{ {
Reporter::conn_weird("Redis_excessive_pipelining", c); Reporter::conn_weird("Redis_excessive_pipelining", c);
# Delete the current state and restart later. We'll be in a weird state, but
# Just spit out what we have # really we want to abort. I don't quite get how to register this as a
while ( c$redis_state$current_response < c$redis_state$current_request ) # violation. :)
{ delete c$redis_state;
local cr = c$redis_state$current_response; return;
if ( cr in c$redis_state$pending )
{
Log::write(Redis::LOG, c$redis_state$pending[cr]);
delete c$redis_state$pending[cr];
}
++c$redis_state$current_response;
}
} }
++c$redis_state$current_request; ++c$redis_state$current_request;
@ -164,9 +186,10 @@ event Redis::command(c: connection, is_orig: bool, command: Command)
if ( to_lower(command$raw[2]) == "on" ) if ( to_lower(command$raw[2]) == "on" )
{ {
# If the last range is open, close it here. Otherwise, noop # If the last range is open, close it here. Otherwise, noop
if ( |c$redis_state$no_response_ranges| > 0 ) if ( |c$redis_state$no_response_ranges| > 0 )
{ {
local range = c$redis_state$no_response_ranges[|c$redis_state$no_response_ranges| - 1]; local range = c$redis_state$no_response_ranges[|c$redis_state$no_response_ranges|
- 1];
if ( |range| == 1 ) if ( |range| == 1 )
{ {
range += c$redis_state$current_request; range += c$redis_state$current_request;
@ -176,16 +199,17 @@ event Redis::command(c: connection, is_orig: bool, command: Command)
if ( to_lower(command$raw[2]) == "off" ) if ( to_lower(command$raw[2]) == "off" )
{ {
# Only add a new interval if the last one is closed # Only add a new interval if the last one is closed
if ( is_last_interval_closed(c) ) if ( is_last_interval_closed(c) )
{ {
c$redis_state$no_response_ranges += vector(c$redis_state$current_request); c$redis_state$no_response_ranges += vector(c$redis_state$current_request);
} }
} }
if ( to_lower(command$raw[2]) == "skip" ) if ( to_lower(command$raw[2]) == "skip" )
{ {
if ( is_last_interval_closed(c) ) if ( is_last_interval_closed(c) )
# It skips this one and the next one # It skips this one and the next one
c$redis_state$no_response_ranges += vector(c$redis_state$current_request, c$redis_state$current_request + 2); c$redis_state$no_response_ranges += vector(c$redis_state$current_request,
c$redis_state$current_request + 2);
} }
} }
} }
@ -202,10 +226,10 @@ function response_num(c: connection): count
for ( i in c$redis_state$no_response_ranges ) for ( i in c$redis_state$no_response_ranges )
{ {
local range = c$redis_state$no_response_ranges[i]; local range = c$redis_state$no_response_ranges[i];
assert |range| >= 1; assert | range | >= 1;
if ( |range| == 1 && resp_num > range[0] ) if ( |range| == 1 && resp_num > range[0] )
{} # TODO: This is necessary if not using pipelining { } # TODO: This is necessary if not using pipelining
if ( |range| == 2 && resp_num >= range[0] && resp_num < range[1] ) if ( |range| == 2 && resp_num >= range[0] && resp_num < range[1] )
return range[1]; return range[1];
} }
@ -215,7 +239,8 @@ function response_num(c: connection): count
event Redis::server_data(c: connection, is_orig: bool, data: ServerData) event Redis::server_data(c: connection, is_orig: bool, data: ServerData)
{ {
if ( ! c?$redis_state ) make_new_state(c); if ( ! c?$redis_state )
make_new_state(c);
local previous_response_num = c$redis_state$current_response; local previous_response_num = c$redis_state$current_response;
c$redis_state$current_response = response_num(c); c$redis_state$current_response = response_num(c);
@ -232,27 +257,37 @@ event Redis::server_data(c: connection, is_orig: bool, data: ServerData)
next; next;
} }
if ( previous_response_num in c$redis_state$pending ) if ( previous_response_num in c$redis_state$pending &&
c$redis_state$pending[previous_response_num]?$cmd )
{ {
Log::write(Redis::LOG, c$redis_state$pending[previous_response_num]); Log::write(Redis::LOG, c$redis_state$pending[previous_response_num]);
delete c$redis_state$pending[previous_response_num]; delete c$redis_state$pending[previous_response_num];
} }
previous_response_num += 1; previous_response_num += 1;
} }
# Log this one # Log this one if we have the request and response
Log::write(Redis::LOG, c$redis); if ( c$redis?$cmd )
delete c$redis_state$pending[c$redis_state$current_response]; {
Log::write(Redis::LOG, c$redis);
delete c$redis_state$pending[c$redis_state$current_response];
}
} }
hook finalize_redis(c: connection) hook finalize_redis(c: connection)
{ {
if ( c$redis_state$violation )
{
# If there's a violation, make sure everything gets deleted
delete c$redis_state;
}
# Flush all pending but incomplete request/response pairs. # Flush all pending but incomplete request/response pairs.
if ( c?$redis_state ) if ( c?$redis_state && c$redis_state$current_response != 0 )
{ {
for ( r, info in c$redis_state$pending ) for ( r, info in c$redis_state$pending )
{ {
# We don't use pending elements at index 0. # We don't use pending elements at index 0.
if ( r == 0 ) next; if ( r == 0 )
next;
Log::write(Redis::LOG, info); Log::write(Redis::LOG, info);
} }
} }

View file

@ -6,6 +6,7 @@ import RESP;
public type KnownCommand = enum { public type KnownCommand = enum {
APPEND, APPEND,
AUTH,
BITCOUNT, BITCOUNT,
BITFIELD, BITFIELD,
BITFIELD_RO, BITFIELD_RO,
@ -234,6 +235,7 @@ function command_from(cmd_bytes: bytes): optional<KnownCommand> {
switch (cmd_bytes.lower()) { switch (cmd_bytes.lower()) {
case b"set": cmd = KnownCommand::SET; case b"set": cmd = KnownCommand::SET;
case b"append": cmd = KnownCommand::APPEND; case b"append": cmd = KnownCommand::APPEND;
case b"auth": cmd = KnownCommand::AUTH;
case b"bitcount": cmd = KnownCommand::BITCOUNT; case b"bitcount": cmd = KnownCommand::BITCOUNT;
case b"bitfield": cmd = KnownCommand::BITFIELD; case b"bitfield": cmd = KnownCommand::BITFIELD;
case b"bitfield_ro": cmd = KnownCommand::BITFIELD_RO; case b"bitfield_ro": cmd = KnownCommand::BITFIELD_RO;
@ -352,3 +354,21 @@ public function make_get(command: Command): Get {
public function is_get(data: RESP::ClientData): bool { 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) == KnownCommand::GET && |data.command.raw| >= 2;
} }
type Auth = struct {
username: optional<bytes>;
password: bytes;
};
public function make_auth(command: Command): Auth {
assert |command.raw| >= 2 : "AUTH must have arguments";
if (|command.raw| == 2) {
return [$username = Null, $password = command.raw[1]];
}
return [$username = command.raw[1], $password = command.raw[2]];
}
public function is_auth(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::AUTH && |data.command.raw| >= 2;
}

View file

@ -12,6 +12,7 @@ export Zeek_Redis::ZeekServerData;
on RESP::ClientData if ( Redis::is_set(self) ) -> event Redis::set_command($conn, $is_orig, Redis::make_set(self.command)); on RESP::ClientData if ( Redis::is_set(self) ) -> event Redis::set_command($conn, $is_orig, Redis::make_set(self.command));
on RESP::ClientData if ( Redis::is_get(self) ) -> event Redis::get_command($conn, $is_orig, Redis::make_get(self.command)); on RESP::ClientData if ( Redis::is_get(self) ) -> event Redis::get_command($conn, $is_orig, Redis::make_get(self.command));
on RESP::ClientData if ( Redis::is_auth(self) ) -> event Redis::auth_command($conn, $is_orig, Redis::make_auth(self.command));
# All client data is a command # All client data is a command
on RESP::ClientData -> event Redis::command($conn, $is_orig, self.command); on RESP::ClientData -> event Redis::command($conn, $is_orig, self.command);

View file

@ -9,7 +9,7 @@ import spicy;
const MAX_SIZE = 1024 * 1024; const MAX_SIZE = 1024 * 1024;
public type ClientMessages = unit { public type ClientMessages = unit {
: (ClientData &synchronize)[]; : ClientData[];
}; };
public type ServerMessages = unit { public type ServerMessages = unit {
@ -17,21 +17,22 @@ public type ServerMessages = unit {
}; };
public type ClientData = unit { public type ClientData = unit {
%synchronize-after = b"\x0d\x0a"; on %init() { self.start = self.input(); }
# Clients can only be an array or inline # Clients can only be an array or inline
ty: uint8 &convert=DataType($$); ty: uint8 &convert=DataType($$) {
if (self.ty != DataType::ARRAY) {
# This is inline, so we need to reparse `ty`
self.set_input(self.start);
}
}
if (self.ty == DataType::ARRAY) { if (self.ty == DataType::ARRAY) {
multibulk: Array; multibulk: Array;
} else { } else {
# HACK: If the type isn'tan array, this is just some random unserialized inline: RedisBytes &max-size=1024;
# string until \r\n - do this by prepending the type to the remaining bytes.
# Formally in Redis code, that's an "inline command."
#
# As an extra point, this is handled in redis in `processInlineBuffer`,
# which has a hardcoded limit of 1024*64. That seems too big. We'll do 1024.
inline: RedisBytes &convert=(pack(cast<uint8>(self.ty), spicy::ByteOrder::Network) + $$) &max-size=1024;
}; };
var start: iterator<stream>;
var command: Redis::Command; var command: Redis::Command;
on %done { on %done {
@ -152,10 +153,10 @@ type Set = unit {
elements: Data[self.num_elements]; elements: Data[self.num_elements];
}; };
on Data::%done { on ServerData::%done {
spicy::accept_input(); spicy::accept_input();
} }
on Data::%error { on ServerData::%error {
spicy::decline_input("error while parsing RESP data"); spicy::decline_input("error while parsing RESP server data");
} }

View file

@ -0,0 +1,12 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.command cmd.key cmd.value response.err response.data
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CtPZjS20MLrsMUOJi2 127.0.0.1 53099 127.0.0.1 6379 AUTH - - F OK
XXXXXXXXXX.XXXXXX CtPZjS20MLrsMUOJi2 127.0.0.1 53099 127.0.0.1 6379 PING - - F OK
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,10 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
AUTH
username: notauser
password: notapassword
AUTH
username: default
password: defaultpasswordinvalid
AUTH
username: noone
password: password

View file

@ -7,17 +7,10 @@
#open XXXX-XX-XX-XX-XX-XX #open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.command cmd.key cmd.value response.err response.data #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.command cmd.key cmd.value response.err response.data
#types time string addr port addr port string string string bool string #types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - - XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - - XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - - XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - - XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 PING - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
@ -26,8 +19,4 @@ XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 57156 ::1 6379 - - - F PONG
#close XXXX-XX-XX-XX-XX-XX #close XXXX-XX-XX-XX-XX-XX

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,8 @@
# @TEST-DOC: Test 2 commands that look like RESP, then server responses don't
#
# @TEST-EXEC: zeek -Cr $TRACES/redis/almost-resp.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff redis.log
#
# Really, the first 2 ARE Redis. The later ones should not be logged because we
# realized it's not Redis. The output from the server is:
# +OK\r\n+OK\r\nnot RESP\r\nStill not RESP\r\nNope\r\n

View file

@ -0,0 +1,16 @@
# @TEST-DOC: Test Zeek with AUTH commands
#
# @TEST-EXEC: zeek -Cr $TRACES/redis/auth.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff output
event Redis::auth_command(c: connection, is_orig: bool,
command: Redis::AuthCommand)
{
print "AUTH";
if ( command?$username )
print fmt("username: %s", command$username);
else
print "username: default";
print fmt("password: %s", command$password);
}

View file

@ -7,7 +7,8 @@
# code directly to the server, but it's useful to see if that trace might come # code directly to the server, but it's useful to see if that trace might come
# up with something different. See: # up with something different. See:
# https://redis.io/docs/latest/develop/use/patterns/bulk-loading/ # https://redis.io/docs/latest/develop/use/patterns/bulk-loading/
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
print fmt("SET: %s %s", command$key, command$value); {
} print fmt("SET: %s %s", command$key, command$value);
}

View file

@ -2,4 +2,3 @@
# #
# @TEST-EXEC: zeek -Cr $TRACES/redis/reply-off-on-2conn.trace base/protocols/redis %INPUT >output # @TEST-EXEC: zeek -Cr $TRACES/redis/reply-off-on-2conn.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log

View file

@ -2,4 +2,3 @@
# #
# @TEST-EXEC: zeek -Cr $TRACES/redis/reply-off-on.trace base/protocols/redis %INPUT >output # @TEST-EXEC: zeek -Cr $TRACES/redis/reply-off-on.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log

View file

@ -2,4 +2,3 @@
# #
# @TEST-EXEC: zeek -Cr $TRACES/redis/client-skip-while-off.trace base/protocols/redis %INPUT >output # @TEST-EXEC: zeek -Cr $TRACES/redis/client-skip-while-off.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log

View file

@ -4,12 +4,12 @@
# @TEST-EXEC: btest-diff output # @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log
redef Redis::ports += { redef Redis::ports += { 10625/tcp, };
10625/tcp,
};
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
# Print the whole command because these have extra data that's worth capturing. {
print fmt("SET: %s %s expires in %d milliseconds", command$key, command$value, command$px); # Print the whole command because these have extra data that's worth capturing.
} print fmt("SET: %s %s expires in %d milliseconds", command$key, command$value,
command$px);
}

View file

@ -4,8 +4,10 @@
# @TEST-EXEC: btest-diff output # @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
# Print the whole command because these have extra data that's worth capturing. {
print fmt("SET: %s %s expires in %d milliseconds", command$key, command$value, command$px); # Print the whole command because these have extra data that's worth capturing.
} print fmt("SET: %s %s expires in %d milliseconds", command$key, command$value,
command$px);
}

View file

@ -7,12 +7,14 @@
# Sometimes commands aren't serialized, like when pipelining. This still works! So we # Sometimes commands aren't serialized, like when pipelining. This still works! So we
# should handle this. This particular example has a few commands, amongst them a SET and # should handle this. This particular example has a few commands, amongst them a SET and
# a GET. # a GET.
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
print fmt("SET: %s %s", command$key, command$value); {
} print fmt("SET: %s %s", command$key, command$value);
}
event Redis::get_command(c: connection, is_orig: bool, command: Redis::GetCommand) event Redis::get_command(c: connection, is_orig: bool,
{ command: Redis::GetCommand)
print fmt("GET: %s", command); {
} print fmt("GET: %s", command);
}

View file

@ -3,7 +3,8 @@
# @TEST-EXEC: zeek -Cr $TRACES/redis/set.trace base/protocols/redis %INPUT >output # @TEST-EXEC: zeek -Cr $TRACES/redis/set.trace base/protocols/redis %INPUT >output
# @TEST-EXEC: btest-diff output # @TEST-EXEC: btest-diff output
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
print fmt("Key: %s Value: %s", command$key, command$value); {
} print fmt("Key: %s Value: %s", command$key, command$value);
}

View file

@ -1,11 +0,0 @@
# @TEST-DOC: Test parsing behavior of RESP.
#
# @TEST-EXEC: spicyc ${DIST}/analyzer/resp.spicy ${DIST}/analyzer/redis.spicy -j -d -o redis.hlto
#
# TODO: A lot of tests are possible from the docs and having them would be nice.
# But, a lot of characters ($, -, etc.) cause problems with TEST_EXEC. ugh.
# @TEST-EXEC: printf "+OK\x0d\x0a" | spicy-dump -p RESP::Data redis.hlto >>output 2>&1
# @TEST-EXEC: printf ":1000\x0d\x0a" | spicy-dump -p RESP::Data redis.hlto >>output 2>&1
# @TEST-EXEC: printf ":-1000\x0d\x0a" | spicy-dump -p RESP::Data redis.hlto >>output 2>&1
# @TEST-EXEC: printf ":+1000\x0d\x0a" | spicy-dump -p RESP::Data redis.hlto >>output 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER= btest-diff output

View file

@ -4,12 +4,14 @@
# @TEST-EXEC: btest-diff output # @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log # @TEST-EXEC: btest-diff redis.log
event Redis::set_command(c: connection, is_orig: bool, command: Redis::SetCommand) event Redis::set_command(c: connection, is_orig: bool,
{ command: Redis::SetCommand)
print fmt("SET: %s %s", command$key, command$value); {
} print fmt("SET: %s %s", command$key, command$value);
}
event Redis::get_command(c: connection, is_orig: bool, command: Redis::GetCommand) event Redis::get_command(c: connection, is_orig: bool,
{ command: Redis::GetCommand)
print fmt("GET: %s", command); {
} print fmt("GET: %s", command);
}