use protofish::decode::UnknownValue;
use protofish::prelude::*;
use protofish::prelude::{Context, FieldValue};
#[derive(Debug, PartialEq)]
pub struct Entry {
pub path: Vec<u64>,
pub value: EntryValue,
}
#[derive(Debug, PartialEq)]
pub enum EntryValue {
Fixed64([u8; 8]),
Fixed32([u8; 4]),
Varint(u128),
Bytes(Vec<u8>),
OpenNested,
CloseNested,
}
#[derive(Copy, Clone)]
pub struct ParseConfig {
pub no_fixed64: bool,
pub no_fixed32: bool,
}
impl Default for ParseConfig {
fn default() -> Self {
Self {
no_fixed64: false,
no_fixed32: false,
}
}
}
pub fn try_parse_entries(bytes: &[u8], config: ParseConfig) -> Option<Vec<Entry>> {
try_parse_entries_inner(bytes, config, &[])
}
fn try_parse_entries_inner(bytes: &[u8], config: ParseConfig, path: &[u64]) -> Option<Vec<Entry>> {
if bytes.is_empty() {
return None;
}
let fields = decode_fields(&bytes);
let mut out = Vec::<Entry>::new();
for field in fields.into_iter() {
let mut nested_path = path.to_vec();
nested_path.push(field.number);
match &field.value {
Value::Unknown(unknown) => match unknown {
UnknownValue::Fixed64(v) => {
if config.no_fixed64 {
return None;
}
out.push(Entry {
path: nested_path,
value: EntryValue::Fixed64(v.to_le_bytes()),
})
}
UnknownValue::Fixed32(v) => {
if config.no_fixed32 {
return None;
}
out.push(Entry {
path: nested_path,
value: EntryValue::Fixed32(v.to_le_bytes()),
})
}
UnknownValue::Varint(v) => out.push(Entry {
path: nested_path,
value: EntryValue::Varint(*v),
}),
UnknownValue::VariableLength(v) => {
if let Some(nested_entries) = try_parse_entries_inner(v, config, &nested_path) {
out.push(Entry {
path: nested_path.clone(),
value: EntryValue::OpenNested,
});
out.extend(nested_entries);
out.push(Entry {
path: nested_path,
value: EntryValue::CloseNested,
});
} else {
out.push(Entry {
path: nested_path,
value: EntryValue::Bytes(v.to_vec()),
})
}
}
UnknownValue::Invalid(_wire_type, _bytes) => {
return None;
}
},
_ => return None,
};
}
Some(out)
}
pub fn decode_fields(bytes: &[u8]) -> Vec<FieldValue> {
let context = Context::parse(&[r#"
syntax = "proto3";
package Proto;
message Empty { }
"#])
.unwrap();
let request = context.get_message("Proto.Empty").unwrap();
let value = request.decode(bytes, &context);
value.fields
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_parse_entries_works() {
let entries = try_parse_entries(b"\x12\x07Unknown", ParseConfig::default()).unwrap();
assert_eq!(
entries,
&[Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
}]
);
let entries =
try_parse_entries(b"\x12\x07Unknown\x12\x07Unknown", ParseConfig::default()).unwrap();
assert_eq!(
entries,
&[
Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
},
Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
}
]
);
let res = try_parse_entries(b"\x12\x07Unknown\x0a\x0fAtlantic ", ParseConfig::default());
assert_eq!(res, None);
let res = try_parse_entries(b"\x14\x07Unknown", ParseConfig::default());
assert_eq!(res, None);
}
#[test]
fn try_parse_entries_returns_none_for_empty() {
let res = try_parse_entries(b"", ParseConfig::default());
assert_eq!(res, None);
}
#[test]
fn try_parse_entries_inner_works() {
let entries =
try_parse_entries_inner(b"\x12\x07Unknown", ParseConfig::default(), &[]).unwrap();
assert_eq!(
entries,
&[Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
}]
);
let entries = try_parse_entries_inner(
b"\x12\x07Unknown\x12\x07Unknown",
ParseConfig::default(),
&[],
)
.unwrap();
assert_eq!(
entries,
&[
Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
},
Entry {
path: vec![2],
value: EntryValue::Bytes(b"Unknown".to_vec())
}
]
);
let entries =
try_parse_entries_inner(b"\x12\x07Unknown", ParseConfig::default(), &[42]).unwrap();
assert_eq!(
entries,
&[Entry {
path: vec![42, 2],
value: EntryValue::Bytes(b"Unknown".to_vec())
}]
);
let res = try_parse_entries_inner(
b"\x12\x07Unknown\x0a\x0fAtlantic ",
ParseConfig::default(),
&[],
);
assert_eq!(res, None);
let res = try_parse_entries_inner(b"\x14\x07Unknown", ParseConfig::default(), &[]);
assert_eq!(res, None);
}
}