Skip to content

Protocol Integration

RESP2/RESP3 frame processing, zero-copy design, and wire protocol details for contributors.

[dependencies]
redis-protocol = { version = "5", features = ["bytes", "codec"] }

Features:

  • bytes - Zero-copy parsing using Bytes type
  • codec - Tokio codec for async streaming

The redis-protocol crate decodes wire data into BytesFrame. We convert to our internal ParsedCommand:

use redis_protocol::resp2::types::BytesFrame;
pub struct ParsedCommand {
pub name: Bytes,
pub args: Vec<Bytes>,
}
pub enum ProtocolError {
EmptyCommand,
ExpectedArray,
InvalidFrame,
Incomplete,
}
impl TryFrom<BytesFrame> for ParsedCommand {
type Error = ProtocolError;
fn try_from(frame: BytesFrame) -> Result<Self, Self::Error> {
match frame {
BytesFrame::Array(frames) => {
let mut iter = frames.into_iter();
let name = iter.next()
.and_then(|f| f.as_bytes())
.ok_or(ProtocolError::EmptyCommand)?;
let args = iter
.filter_map(|f| f.as_bytes())
.collect();
Ok(ParsedCommand { name, args })
}
_ => Err(ProtocolError::ExpectedArray),
}
}
}

The Response enum includes both RESP2 types and RESP3 types:

pub enum Response {
// === RESP2 Types ===
Simple(Bytes), // +OK\r\n
Error(Bytes), // -ERR message\r\n
Integer(i64), // :1000\r\n
Bulk(Option<Bytes>), // $5\r\nhello\r\n or $-1\r\n (null)
Array(Vec<Response>), // *2\r\n...
// === RESP3 Types ===
Null, // _\r\n
Double(f64), // ,3.14159\r\n
Boolean(bool), // #t\r\n or #f\r\n
BlobError(Bytes), // !<len>\r\n<bytes>\r\n
VerbatimString { // =<len>\r\n<fmt>:<data>\r\n
format: [u8; 3],
data: Bytes,
},
Map(Vec<(Response, Response)>), // %<count>\r\n<key><value>...
Set(Vec<Response>), // ~<count>\r\n<elements>...
Attribute(Box<Response>), // |<count>\r\n<attr-map><data>
Push(Vec<Response>), // ><count>\r\n<elements>...
BigNumber(Bytes), // (<big-integer>\r\n
}
pub enum ProtocolVersion {
Resp2, // Default
Resp3, // Negotiated via HELLO command
}

TypeWire FormatUse Case
Null_\r\nExplicit null (vs RESP2’s overloaded $-1)
Double,3.14\r\nFloating point (scores, etc.)
Boolean#t\r\n / #f\r\nTrue/false values
Map%<n>\r\n...Key-value pairs (HGETALL, etc.)
Set~<n>\r\n...Unordered unique elements
Push><n>\r\n...Out-of-band pub/sub messages
BigNumber(<num>\r\nArbitrary precision integers
VerbatimString=<n>\r\n<fmt>:...Formatted text (markdown, etc.)
Attribute`\r\n…`
  • Type-rich responses: Maps, sets, booleans reduce client parsing ambiguity
  • Out-of-band push: Cleaner pub/sub without inline message interleaving
  • Explicit nulls: _\r\n vs RESP2’s overloaded $-1\r\n
  • Native doubles: No string conversion needed for ZSCORE, etc.

All new connections start in RESP2 mode. Clients send HELLO 3 to upgrade to RESP3.

HELLO VersionBehavior
HELLO (no version)Return connection info in current protocol
HELLO 2Downgrade to RESP2 (if in RESP3), return info in RESP2
HELLO 3Upgrade to RESP3, return info in RESP3
HELLO 4+Return -NOPROTO unsupported protocol version

Once a connection has upgraded to RESP3, it cannot downgrade within the same session. Clients that never send HELLO operate exactly as they would with Redis (default RESP2).

Server-assisted client-side caching via RESP3 Push invalidation messages. When a client reads a key with tracking enabled, the server records the association. When that key is later modified, the server sends a >invalidate [keys] Push frame so the client can evict its local cache.

Supported modes: Default, OPTIN, OPTOUT, NOLOOP, BCAST, PREFIX, REDIRECT.

Use the built-in codec for connection handling:

use redis_protocol::resp2::codec::Resp2;
use tokio_util::codec::Framed;
let framed = Framed::new(socket, Resp2::default());
Error TypeHandling
Incomplete frameCodec buffers, waits for more data
Malformed frameReturn -ERR response, continue
Invalid commandReturn -ERR unknown command, continue

Connections are not closed on protocol errors (matches Redis behavior).

Recoverable Errors (Send -ERR, Continue):

ErrorResponseContinue?
Empty array-ERR empty commandYes
Non-array top-level-ERR commands must be arraysYes
Non-bulk-string args-ERR arguments must be bulk stringsYes
Command too long-ERR command too longYes

Unrecoverable Errors (Close Connection):

ErrorClose Reason
Invalid type byteProtocol corruption
Negative bulk lengthProtocol corruption
Overflow lengthDoS protection
Invalid integer parseProtocol corruption
Missing CRLF terminatorProtocol corruption
SettingDefaultDescription
frame_timeout_ms30000Timeout for incomplete frames
max_frame_size536870912Maximum frame size (512MB, matches Redis)
max_bulk_string_length536870912Maximum bulk string length
max_array_elements1000000Maximum array elements