Skip to content

Navigation Menu

Sign in
Sign up

Adding a New Protocol

lex edited this page May 28, 2026 · 1 revision

Adding a New Protocol

This guide walks you through adding support for a new wire protocol to Ocular.


Overview

Adding a protocol requires changes in 4 files (all in crates/ocular-protocol/) plus tests. No changes needed in the proxy, capture, or TUI crates — the ProtocolHandler trait abstracts everything.


Step 1: Create the Parser Module

Create crates/ocular-protocol/src/yourprotocol.rs:

/// Parse a request buffer into a human-readable command string.
/// Return None if the buffer doesn't contain a complete parseable request.
pub fn parse_request(buf: &[u8]) -> Option<String> {
 // Example: parse a text-based protocol
 let text = std::str::from_utf8(buf).ok()?;
 let line = text.lines().next()?;
 Some(line.to_string())
}
/// Parse a response buffer into a human-readable summary.
/// Return None if unparseable.
pub fn parse_response(buf: &[u8]) -> Option<String> {
 let text = std::str::from_utf8(buf).ok()?;
 Some(text.lines().next()?.to_string())
}
/// Format the full response for the detail pane.
/// Return None to use the default (raw bytes as UTF-8).
pub fn format_response_detail(buf: &[u8]) -> Option<String> {
 Some(String::from_utf8_lossy(buf).to_string())
}
/// Extract the full command string for display and filtering.
/// For SQL-like protocols, this is the full query text.
pub fn extract_full_command(buf: &[u8]) -> Option<String> {
 parse_request(buf)
}
#[cfg(test)]
mod tests {
 use super::*;
 #[test]
 fn test_parse_simple_request() {
 let buf = b"GET key1\r\n";
 assert_eq!(parse_request(buf), Some("GET key1".to_string()));
 }
 #[test]
 fn test_parse_incomplete_request() {
 let buf = b"GET ke";
 assert_eq!(parse_request(buf), None);
 }
}

Step 2: Implement the ProtocolHandler Trait

In crates/ocular-protocol/src/handlers.rs, add:

use crate::yourprotocol;
pub struct YourProtocolHandler;
impl ProtocolHandler for YourProtocolHandler {
 fn parse_request(&self, buf: &[u8]) -> Option<String> {
 yourprotocol::parse_request(buf)
 }
 fn parse_response(&self, buf: &[u8]) -> Option<String> {
 yourprotocol::parse_response(buf)
 }
 fn format_response_detail(&self, buf: &[u8]) -> Option<String> {
 yourprotocol::format_response_detail(buf)
 }
 fn extract_full_command(&self, buf: &[u8]) -> Option<String> {
 yourprotocol::extract_full_command(buf)
 }
 fn default_port(&self) -> u16 {
 12345 // The well-known port for your protocol
 }
 // If your protocol needs multi-packet buffering:
 // fn needs_request_buffering(&self) -> bool { true }
 // fn needs_response_buffering(&self) -> bool { true }
 // fn request_complete(&self, buf: &[u8]) -> bool { /* check delimiter */ }
 // fn response_complete(&self, buf: &[u8]) -> bool { /* check length header */ }
 // If your protocol has a handshake (like MySQL auth):
 // fn capture_handshake(&self, buf: &[u8]) -> Option<HandshakeAction> {
 // Some(HandshakeAction::Done)
 // }
}

Step 3: Register the Protocol

In crates/ocular-protocol/src/lib.rs:

3a. Add the module

pub mod yourprotocol;

3b. Add enum variant

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Protocol {
 Redis,
 Mysql,
 Postgres,
 Amqp,
 Mongodb,
 Memcached,
 Kafka,
 Http,
 YourProtocol, // ← new
}

3c. Add name mapping in from_str

impl Protocol {
 pub fn from_str(s: &str) -> Option<Self> {
 match s.to_lowercase().as_str() {
 "redis" | "resp" => Some(Self::Redis),
 "mysql" => Some(Self::Mysql),
 // ...
 "yourprotocol" | "yp" => Some(Self::YourProtocol), // ← new (add alias)
 _ => None,
 }
 }
}

3d. Add handler in get_handler

pub fn get_handler(protocol: Protocol) -> Box<dyn ProtocolHandler> {
 match protocol {
 Protocol::Redis => Box::new(handlers::RedisHandler),
 // ...
 Protocol::YourProtocol => Box::new(handlers::YourProtocolHandler), // ← new
 }
}

Step 4: Handle Buffering (If Needed)

Most text-based protocols (line-delimited) don't need special buffering. But if your protocol:

  • Uses length-prefixed messages (like Kafka, MySQL): implement message_length() so capture mode knows where one message ends and the next begins
  • Has multi-packet responses (large result sets): implement needs_response_buffering() = true and response_complete()
  • Has a handshake phase (like MySQL auth): implement capture_handshake() to tell capture mode when to start parsing data

Example for a length-prefixed protocol:

fn message_length(&self, buf: &[u8]) -> Option<usize> {
 if buf.len() < 4 { return None; }
 let len = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
 Some(4 + len) // header + body
}

Step 5: Test

cargo test -p ocular-protocol

Integration test with a real service

# Start the service
docker run -d --rm -p 12345:12345 yourservice/yourimage
# Test with Ocular
ocular proxy yourprotocol 127.0.0.1:12345

Checklist

  • Parser module created with parse_request, parse_response, format_response_detail, extract_full_command
  • ProtocolHandler trait implemented
  • Protocol registered in lib.rs (module, enum, from_str, get_handler)
  • Unit tests for parser edge cases (empty buffer, incomplete message, malformed data)
  • default_port() returns the well-known port
  • Buffering methods implemented if needed
  • cargo test passes
  • Manual test with real service via ocular proxy

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /