Test infra, handle RESP3 attributes and big-numbers and bools (#9235)

- promote the code in DEBUG PROTOCOL to addReplyBigNum
- DEBUG PROTOCOL ATTRIB skips the attribute when client is RESP2
- networking.c addReply for push and attributes generate assertion when
  called on a RESP2 client, anything else would produce a broken
  protocol that clients can't handle.

(cherry picked from commit 6a5bac309e868deef749c36949723b415de2496f)
This commit is contained in:
Oran Agra 2021-07-14 19:14:31 +03:00
parent 519955ecf2
commit 8149476f66
5 changed files with 115 additions and 34 deletions

View File

@ -721,7 +721,7 @@ NULL
} else if (!strcasecmp(name,"double")) { } else if (!strcasecmp(name,"double")) {
addReplyDouble(c,3.14159265359); addReplyDouble(c,3.14159265359);
} else if (!strcasecmp(name,"bignum")) { } else if (!strcasecmp(name,"bignum")) {
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40); addReplyBigNum(c,"1234567999999999999999999999999999999",37);
} else if (!strcasecmp(name,"null")) { } else if (!strcasecmp(name,"null")) {
addReplyNull(c); addReplyNull(c);
} else if (!strcasecmp(name,"array")) { } else if (!strcasecmp(name,"array")) {
@ -737,11 +737,13 @@ NULL
addReplyBool(c, j == 1); addReplyBool(c, j == 1);
} }
} else if (!strcasecmp(name,"attrib")) { } else if (!strcasecmp(name,"attrib")) {
addReplyAttributeLen(c,1); if (c->resp >= 3) {
addReplyBulkCString(c,"key-popularity"); addReplyAttributeLen(c,1);
addReplyArrayLen(c,2); addReplyBulkCString(c,"key-popularity");
addReplyBulkCString(c,"key:123"); addReplyArrayLen(c,2);
addReplyLongLong(c,90); addReplyBulkCString(c,"key:123");
addReplyLongLong(c,90);
}
/* Attributes are not real replies, so a well formed reply should /* Attributes are not real replies, so a well formed reply should
* also have a normal reply type after the attribute. */ * also have a normal reply type after the attribute. */
addReplyBulkCString(c,"Some real reply following the attribute"); addReplyBulkCString(c,"Some real reply following the attribute");

View File

@ -649,14 +649,13 @@ void setDeferredSetLen(client *c, void *node, long length) {
} }
void setDeferredAttributeLen(client *c, void *node, long length) { void setDeferredAttributeLen(client *c, void *node, long length) {
int prefix = c->resp == 2 ? '*' : '|'; serverAssert(c->resp >= 3);
if (c->resp == 2) length *= 2; setDeferredAggregateLen(c,node,length,'|');
setDeferredAggregateLen(c,node,length,prefix);
} }
void setDeferredPushLen(client *c, void *node, long length) { void setDeferredPushLen(client *c, void *node, long length) {
int prefix = c->resp == 2 ? '*' : '>'; serverAssert(c->resp >= 3);
setDeferredAggregateLen(c,node,length,prefix); setDeferredAggregateLen(c,node,length,'>');
} }
/* Add a double as a bulk reply */ /* Add a double as a bulk reply */
@ -685,6 +684,16 @@ void addReplyDouble(client *c, double d) {
} }
} }
void addReplyBigNum(client *c, const char* num, size_t len) {
if (c->resp == 2) {
addReplyBulkCBuffer(c, num, len);
} else {
addReplyProto(c,"(",1);
addReplyProto(c,num,len);
addReply(c,shared.crlf);
}
}
/* Add a long double as a bulk reply, but uses a human readable formatting /* Add a long double as a bulk reply, but uses a human readable formatting
* of the double instead of exposing the crude behavior of doubles to the * of the double instead of exposing the crude behavior of doubles to the
* dear user. */ * dear user. */
@ -756,14 +765,13 @@ void addReplySetLen(client *c, long length) {
} }
void addReplyAttributeLen(client *c, long length) { void addReplyAttributeLen(client *c, long length) {
int prefix = c->resp == 2 ? '*' : '|'; serverAssert(c->resp >= 3);
if (c->resp == 2) length *= 2; addReplyAggregateLen(c,length,'|');
addReplyAggregateLen(c,length,prefix);
} }
void addReplyPushLen(client *c, long length) { void addReplyPushLen(client *c, long length) {
int prefix = c->resp == 2 ? '*' : '>'; serverAssert(c->resp >= 3);
addReplyAggregateLen(c,length,prefix); addReplyAggregateLen(c,length,'>');
} }
void addReplyNull(client *c) { void addReplyNull(client *c) {

View File

@ -1840,6 +1840,7 @@ void addReplyErrorSds(client *c, sds err);
void addReplyError(client *c, const char *err); void addReplyError(client *c, const char *err);
void addReplyStatus(client *c, const char *status); void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d); void addReplyDouble(client *c, double d);
void addReplyBigNum(client *c, const char* num, size_t len);
void addReplyHumanLongDouble(client *c, long double d); void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll); void addReplyLongLong(client *c, long long ll);
void addReplyArrayLen(client *c, long length); void addReplyArrayLen(client *c, long length);

View File

@ -247,30 +247,46 @@ proc ::redis::redis_read_null fd {
return {} return {}
} }
proc ::redis::redis_read_bool fd {
set v [redis_read_line $fd]
if {$v == "t"} {return 1}
if {$v == "f"} {return 0}
return -code error "Bad protocol, '$v' as bool type"
}
proc ::redis::redis_read_reply {id fd} { proc ::redis::redis_read_reply {id fd} {
if {$::redis::readraw($id)} { if {$::redis::readraw($id)} {
return [redis_read_line $fd] return [redis_read_line $fd]
} }
set type [read $fd 1] while {1} {
switch -exact -- $type { set type [read $fd 1]
_ {redis_read_null $fd} switch -exact -- $type {
: - _ {return [redis_read_null $fd]}
+ {redis_read_line $fd} : -
, {expr {double([redis_read_line $fd])}} ( -
- {return -code error [redis_read_line $fd]} + {return [redis_read_line $fd]}
$ {redis_bulk_read $fd} , {return [expr {double([redis_read_line $fd])}]}
> - # {return [redis_read_bool $fd]}
~ - - {return -code error [redis_read_line $fd]}
* {redis_multi_bulk_read $id $fd} $ {return [redis_bulk_read $fd]}
% {redis_read_map $id $fd} > -
default { ~ -
if {$type eq {}} { * {return [redis_multi_bulk_read $id $fd]}
catch {close $fd} % {return [redis_read_map $id $fd]}
set ::redis::fd($id) {} | {
return -code error "I/O error reading reply" # ignore attributes for now (nowhere to store them)
redis_read_map $id $fd
continue
}
default {
if {$type eq {}} {
catch {close $fd}
set ::redis::fd($id) {}
return -code error "I/O error reading reply"
}
return -code error "Bad protocol, '$type' as reply type byte"
} }
return -code error "Bad protocol, '$type' as reply type byte"
} }
} }
} }

View File

@ -136,6 +136,60 @@ start_server {tags {"protocol network"}} {
# check the connection still works # check the connection still works
assert_equal [r ping] {PONG} assert_equal [r ping] {PONG}
test {RESP3 attributes} {
r hello 3
set res [r debug protocol attrib]
# currently the parser in redis.tcl ignores the attributes
# restore state
r hello 2
set _ $res
} {Some real reply following the attribute}
test {RESP3 attributes readraw} {
r hello 3
r readraw 1
r deferred 1
r debug protocol attrib
assert_equal [r read] {|1}
assert_equal [r read] {$14}
assert_equal [r read] {key-popularity}
assert_equal [r read] {*2}
assert_equal [r read] {$7}
assert_equal [r read] {key:123}
assert_equal [r read] {:90}
assert_equal [r read] {$39}
assert_equal [r read] {Some real reply following the attribute}
# restore state
r readraw 0
r deferred 0
r hello 2
set _ {}
} {}
test {RESP3 attributes on RESP2} {
r hello 2
set res [r debug protocol attrib]
set _ $res
} {Some real reply following the attribute}
test "test big number parsing" {
r hello 3
r debug protocol bignum
} {1234567999999999999999999999999999999}
test "test bool parsing" {
r hello 3
assert_equal [r debug protocol true] 1
assert_equal [r debug protocol false] 0
r hello 2
assert_equal [r debug protocol true] 1
assert_equal [r debug protocol false] 0
set _ {}
} {}
} }
start_server {tags {"regression"}} { start_server {tags {"regression"}} {