use bytes::Buf;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireType {
Varint,
Bit64,
LengthDelimited,
StartGroup,
EndGroup,
Bit32,
Unknown(u32),
}
impl fmt::Display for WireType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WireType::Varint => write!(f, "varint"),
WireType::Bit64 => write!(f, "64-bit"),
WireType::LengthDelimited => write!(f, "length-delimited"),
WireType::StartGroup => write!(f, "start-group"),
WireType::EndGroup => write!(f, "end-group"),
WireType::Bit32 => write!(f, "32-bit"),
WireType::Unknown(v) => write!(f, "unknown({})", v),
}
}
}
impl From<u32> for WireType {
fn from(value: u32) -> Self {
match value {
0 => WireType::Varint,
1 => WireType::Bit64,
2 => WireType::LengthDelimited,
3 => WireType::StartGroup,
4 => WireType::EndGroup,
5 => WireType::Bit32,
v => WireType::Unknown(v),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProtoField {
pub field_number: u32,
pub wire_type: WireType,
pub value: ProtoValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProtoValue {
Varint(u64),
Fixed32(u32),
Fixed64(u64),
Bytes(Vec<u8>),
Nested(Vec<ProtoField>),
}
impl fmt::Display for ProtoValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProtoValue::Varint(v) => write!(f, "{}", v),
ProtoValue::Fixed32(v) => write!(f, "0x{:08x}", v),
ProtoValue::Fixed64(v) => write!(f, "0x{:016x}", v),
ProtoValue::Bytes(b) => {
if let Ok(s) = std::str::from_utf8(b)
&& s.chars()
.all(|c| !c.is_control() || c == '\n' || c == '\r' || c == '\t')
{
return write!(f, "\"{}\"", s);
}
write!(f, "0x")?;
for byte in b {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
ProtoValue::Nested(fields) => {
write!(f, "{{ ")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", field)?;
}
write!(f, " }}")
}
}
}
}
impl fmt::Display for ProtoField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"field {} ({}): {}",
self.field_number, self.wire_type, self.value
)
}
}
fn decode_varint(buf: &[u8]) -> Option<(u64, usize)> {
let mut value: u64 = 0;
let mut shift = 0;
for (i, &byte) in buf.iter().enumerate() {
if shift >= 64 {
return None;
}
value |= ((byte & 0x7F) as u64) << shift;
shift += 7;
if byte & 0x80 == 0 {
return Some((value, i + 1));
}
}
None
}
pub fn decode_protobuf(data: &[u8]) -> Option<Vec<ProtoField>> {
let mut fields = Vec::new();
let mut pos = 0;
while pos < data.len() {
let (tag, tag_len) = decode_varint(&data[pos..])?;
pos += tag_len;
let wire_type_raw = (tag & 0x07) as u32;
let field_number = (tag >> 3) as u32;
if field_number == 0 {
return None;
}
let wire_type = WireType::from(wire_type_raw);
let value = match wire_type {
WireType::Varint => {
let (v, v_len) = decode_varint(&data[pos..])?;
pos += v_len;
ProtoValue::Varint(v)
}
WireType::Bit64 => {
if pos + 8 > data.len() {
return None;
}
let mut buf = &data[pos..pos + 8];
let v = buf.get_u64_le();
pos += 8;
ProtoValue::Fixed64(v)
}
WireType::LengthDelimited => {
let (len, len_bytes) = decode_varint(&data[pos..])?;
pos += len_bytes;
let len = len as usize;
if pos + len > data.len() {
return None;
}
let payload = &data[pos..pos + len];
pos += len;
if let Some(nested) = decode_protobuf(payload) {
if !nested.is_empty() {
ProtoValue::Nested(nested)
} else {
ProtoValue::Bytes(payload.to_vec())
}
} else {
ProtoValue::Bytes(payload.to_vec())
}
}
WireType::Bit32 => {
if pos + 4 > data.len() {
return None;
}
let mut buf = &data[pos..pos + 4];
let v = buf.get_u32_le();
pos += 4;
ProtoValue::Fixed32(v)
}
WireType::StartGroup | WireType::EndGroup => {
return None;
}
WireType::Unknown(_) => {
return None;
}
};
fields.push(ProtoField {
field_number,
wire_type,
value,
});
}
Some(fields)
}
#[derive(Debug)]
pub struct GrpcFrame {
pub compressed: bool,
pub payload: Vec<u8>,
pub decoded_fields: Option<Vec<ProtoField>>,
}
impl fmt::Display for GrpcFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"gRPC frame (compressed={}, {} bytes)",
self.compressed,
self.payload.len()
)?;
if let Some(ref fields) = self.decoded_fields {
for field in fields {
write!(f, "\n {}", field)?;
}
} else {
write!(f, "\n <raw bytes: {} bytes>", self.payload.len())?;
}
Ok(())
}
}
pub fn parse_grpc_frames(data: &[u8]) -> Vec<GrpcFrame> {
let mut frames = Vec::new();
let mut pos = 0;
while pos + 5 <= data.len() {
let compressed = data[pos] != 0;
pos += 1;
let length =
u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if pos + length > data.len() {
break;
}
let payload = data[pos..pos + length].to_vec();
pos += length;
let decoded_fields = if !compressed {
decode_protobuf(&payload)
} else {
None
};
frames.push(GrpcFrame {
compressed,
payload,
decoded_fields,
});
}
frames
}
pub fn hex_dump(data: &[u8], max_bytes: usize) -> String {
let truncated = data.len() > max_bytes;
let display = &data[..data.len().min(max_bytes)];
let mut result = String::new();
for (i, chunk) in display.chunks(16).enumerate() {
if i > 0 {
result.push('\n');
}
result.push_str(&format!(" {:04x}: ", i * 16));
for (j, byte) in chunk.iter().enumerate() {
if j == 8 {
result.push(' ');
}
result.push_str(&format!("{:02x} ", byte));
}
let remaining = 16 - chunk.len();
for j in 0..remaining {
if chunk.len() + j == 8 {
result.push(' ');
}
result.push_str(" ");
}
result.push_str(" |");
for byte in chunk {
if byte.is_ascii_graphic() || *byte == b' ' {
result.push(*byte as char);
} else {
result.push('.');
}
}
result.push('|');
}
if truncated {
result.push_str(&format!(
"\n ... ({} bytes truncated)",
data.len() - max_bytes
));
}
result
}
pub fn format_grpc_message(data: &[u8]) -> String {
let frames = parse_grpc_frames(data);
if frames.is_empty() {
return format!(" <no valid gRPC frames, {} raw bytes>", data.len());
}
let mut result = String::new();
for (i, frame) in frames.iter().enumerate() {
if i > 0 {
result.push('\n');
}
result.push_str(&format!("{}", frame));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_varint() {
assert_eq!(decode_varint(&[0x01]), Some((1, 1)));
assert_eq!(decode_varint(&[0x7F]), Some((127, 1)));
assert_eq!(decode_varint(&[0x80, 0x01]), Some((128, 2)));
assert_eq!(decode_varint(&[0xAC, 0x02]), Some((300, 2)));
}
#[test]
fn test_decode_simple_protobuf() {
let data = vec![0x08, 0x96, 0x01];
let fields = decode_protobuf(&data).unwrap();
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].field_number, 1);
assert_eq!(fields[0].wire_type, WireType::Varint);
match &fields[0].value {
ProtoValue::Varint(v) => assert_eq!(*v, 150),
_ => panic!("Expected varint"),
}
}
#[test]
fn test_decode_string_field() {
let data = vec![0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67];
let fields = decode_protobuf(&data).unwrap();
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].field_number, 2);
assert_eq!(fields[0].wire_type, WireType::LengthDelimited);
match &fields[0].value {
ProtoValue::Bytes(b) => assert_eq!(b, b"testing"),
_ => panic!("Expected bytes"),
}
}
#[test]
fn test_parse_grpc_frame() {
let mut data = vec![0x00]; data.extend_from_slice(&3u32.to_be_bytes()); data.extend_from_slice(&[0x08, 0x96, 0x01]);
let frames = parse_grpc_frames(&data);
assert_eq!(frames.len(), 1);
assert!(!frames[0].compressed);
assert!(frames[0].decoded_fields.is_some());
let fields = frames[0].decoded_fields.as_ref().unwrap();
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].field_number, 1);
}
#[test]
fn test_invalid_protobuf() {
let data = vec![
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];
assert!(decode_protobuf(&data).is_none());
}
#[test]
fn test_hex_dump() {
let data = b"Hello, World!";
let dump = hex_dump(data, 256);
assert!(dump.contains("48 65 6c 6c"));
assert!(dump.contains("|Hello, World!|"));
}
#[test]
fn test_empty_data() {
let fields = decode_protobuf(&[]);
assert_eq!(fields, Some(vec![]));
}
#[test]
fn test_wire_type_display() {
assert_eq!(format!("{}", WireType::Varint), "varint");
assert_eq!(format!("{}", WireType::LengthDelimited), "length-delimited");
assert_eq!(format!("{}", WireType::Unknown(99)), "unknown(99)");
}
}