use serde::Serialize;
#[derive(Serialize)]
#[serde(tag = "kind")]
pub enum ProtoValue {
#[serde(rename = "varint")]
Varint { value: u64 },
#[serde(rename = "fixed64")]
Fixed64 { value: u64 },
#[serde(rename = "fixed32")]
Fixed32 { value: u32 },
#[serde(rename = "bytes")]
Bytes {
hex: String,
text: Option<String>,
nested: Option<Vec<ProtoField>>,
},
}
#[derive(Serialize)]
pub struct ProtoField {
pub field_number: u64,
pub wire_type: u8,
pub value: ProtoValue,
}
fn read_varint(bytes: &[u8], pos: &mut usize) -> Result<u64, String> {
let mut result: u64 = 0;
let mut shift = 0;
loop {
if *pos >= bytes.len() {
return Err("Unexpected end of input while reading varint".to_string());
}
let byte = bytes[*pos];
*pos += 1;
result |= ((byte & 0x7F) as u64) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift >= 64 {
return Err("Varint too long".to_string());
}
}
Ok(result)
}
fn decode_fields(bytes: &[u8]) -> Result<Vec<ProtoField>, String> {
let mut fields = Vec::new();
let mut pos = 0;
while pos < bytes.len() {
let tag = read_varint(bytes, &mut pos)?;
let field_number = tag >> 3;
let wire_type = (tag & 0x7) as u8;
if field_number == 0 {
return Err("Invalid field number 0".to_string());
}
let value = match wire_type {
0 => ProtoValue::Varint {
value: read_varint(bytes, &mut pos)?,
},
1 => {
if pos + 8 > bytes.len() {
return Err("Unexpected end of input while reading fixed64".to_string());
}
let v = u64::from_le_bytes(bytes[pos..pos + 8].try_into().unwrap());
pos += 8;
ProtoValue::Fixed64 { value: v }
}
2 => {
let len = read_varint(bytes, &mut pos)? as usize;
if pos + len > bytes.len() {
return Err(
"Unexpected end of input while reading length-delimited field".to_string(),
);
}
let slice = &bytes[pos..pos + len];
pos += len;
let text = std::str::from_utf8(slice)
.ok()
.filter(|s| {
!s.is_empty() && s.chars().all(|c| !c.is_control() || c.is_whitespace())
})
.map(|s| s.to_string());
let nested = decode_fields(slice).ok().filter(|f| !f.is_empty());
ProtoValue::Bytes {
hex: hex::encode(slice),
text,
nested,
}
}
5 => {
if pos + 4 > bytes.len() {
return Err("Unexpected end of input while reading fixed32".to_string());
}
let v = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap());
pos += 4;
ProtoValue::Fixed32 { value: v }
}
other => return Err(format!("Unsupported wire type {other}")),
};
fields.push(ProtoField {
field_number,
wire_type,
value,
});
}
Ok(fields)
}
fn parse_hex_input(hex: &str) -> Result<Vec<u8>, String> {
let clean: String = hex.chars().filter(|c| !c.is_whitespace()).collect();
hex::decode(&clean).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn decode_protobuf(hex: String) -> Result<Vec<ProtoField>, String> {
let bytes = parse_hex_input(&hex)?;
decode_fields(&bytes)
}
#[tauri::command]
pub fn decode_msgpack(hex: String) -> Result<serde_json::Value, String> {
let bytes = parse_hex_input(&hex)?;
rmp_serde::from_slice::<serde_json::Value>(&bytes).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn read_file_hex(path: String) -> Result<String, String> {
let bytes = std::fs::read(&path).map_err(|e| e.to_string())?;
Ok(hex::encode(bytes))
}