#![cfg(all(feature = "reflect", feature = "json", feature = "std"))]
use std::sync::Arc;
use buffa::encoding::encode_varint;
use buffa::RECURSION_LIMIT;
use buffa_descriptor::reflect::{DynamicMessage, ReflectMessageMut, Value};
use buffa_descriptor::DescriptorPool;
const FDS_BYTES: &[u8] = include_bytes!("protos/reflect_test_options.fds");
const ANY_URL: &str = "type.googleapis.com/google.protobuf.Any";
const ENVELOPE_URL: &str = "type.googleapis.com/reflect.opt.Envelope";
const DESCRIPTOR_PROTO_URL: &str = "type.googleapis.com/google.protobuf.DescriptorProto";
const LIMIT: usize = RECURSION_LIMIT as usize;
const STACK_FLOOR: usize = 1024 * 1024;
fn pool() -> Arc<DescriptorPool> {
Arc::new(DescriptorPool::decode(FDS_BYTES).expect("pool builds from protoc FDS"))
}
struct Frame(Vec<u8>);
impl Frame {
fn any(url: &str) -> Self {
let mut head = vec![0x0A];
encode_varint(url.len() as u64, &mut head);
head.extend_from_slice(url.as_bytes());
head.push(0x12);
Self(head)
}
fn envelope() -> Self {
Self(vec![0x0A])
}
fn descriptor_proto() -> Self {
Self(vec![0x1A])
}
}
fn encode_nested(frames: &[Frame]) -> Vec<u8> {
let mut payload_len = vec![0usize; frames.len() + 1];
for (i, Frame(head)) in frames.iter().enumerate().rev() {
let inner = payload_len[i + 1];
payload_len[i] = head.len() + varint_len(inner) + inner;
}
let mut out = Vec::with_capacity(payload_len[0]);
for (i, Frame(head)) in frames.iter().enumerate() {
out.extend_from_slice(head);
encode_varint(payload_len[i + 1] as u64, &mut out);
}
assert_eq!(out.len(), payload_len[0]);
out
}
fn varint_len(n: usize) -> usize {
let mut buf = Vec::new();
encode_varint(n as u64, &mut buf);
buf.len()
}
fn to_json(type_name: &str, bytes: &[u8]) -> Result<String, serde_json::Error> {
let p = pool();
let idx = p.message_index(type_name).unwrap();
let msg = DynamicMessage::decode(p, idx, bytes)
.expect("binary decode succeeds: Any payloads are opaque to it");
on_floor(msg)
}
fn on_floor(msg: DynamicMessage) -> Result<String, serde_json::Error> {
std::thread::Builder::new()
.stack_size(STACK_FLOOR)
.spawn(move || msg.to_json())
.unwrap()
.join()
.unwrap()
}
fn assert_too_deep(result: Result<String, serde_json::Error>) {
let err = result.expect_err("over-deep nesting must be a serde error, not a stack overflow");
let text = err.to_string();
let needle = format!("nesting depth exceeds buffa::RECURSION_LIMIT ({RECURSION_LIMIT})");
assert!(text.contains(&needle), "unexpected error: {text}");
}
#[test]
fn deeply_nested_any_is_an_error_not_an_abort() {
let frames: Vec<Frame> = (0..20_000).map(|_| Frame::any(ANY_URL)).collect();
let bytes = encode_nested(&frames);
assert!(bytes.len() < 1024 * 1024, "{} bytes", bytes.len());
assert_too_deep(to_json("google.protobuf.Any", &bytes));
}
#[test]
fn any_nesting_at_the_limit_serializes() {
let frames: Vec<Frame> = (0..LIMIT).map(|_| Frame::any(ANY_URL)).collect();
let json = to_json("google.protobuf.Any", &encode_nested(&frames)).expect("at limit");
assert_eq!(json.matches("\"@type\"").count(), LIMIT);
assert!(
json.ends_with(&format!("{{}}{}", "}".repeat(LIMIT))),
"{json}"
);
let frames: Vec<Frame> = (0..=LIMIT).map(|_| Frame::any(ANY_URL)).collect();
assert_too_deep(to_json("google.protobuf.Any", &encode_nested(&frames)));
}
#[test]
fn any_spreading_a_plain_message_shares_one_budget() {
let pairs = |n: usize| -> Vec<Frame> {
(0..n)
.flat_map(|_| [Frame::envelope(), Frame::any(ENVELOPE_URL)])
.collect()
};
let json =
to_json("reflect.opt.Envelope", &encode_nested(&pairs(LIMIT / 2))).expect("at limit");
assert_eq!(json.matches("\"payload\"").count(), LIMIT / 2);
let mut over = pairs(LIMIT / 2);
over.push(Frame::envelope());
assert_too_deep(to_json("reflect.opt.Envelope", &encode_nested(&over)));
assert_too_deep(to_json(
"reflect.opt.Envelope",
&encode_nested(&pairs(5_000)),
));
}
#[test]
fn programmatically_built_over_deep_message_is_an_error() {
let p = pool();
let idx = p.message_index("google.protobuf.DescriptorProto").unwrap();
let nested_type = p.message(idx).field(3).unwrap();
let build = |levels: usize| {
let mut msg = DynamicMessage::new(Arc::clone(&p), idx);
for _ in 0..levels {
let mut outer = DynamicMessage::new(Arc::clone(&p), idx);
outer.set(nested_type, Value::List(vec![Value::Message(msg)]));
msg = outer;
}
msg
};
on_floor(build(LIMIT)).expect("at limit");
assert_too_deep(on_floor(build(LIMIT + 1)));
}
#[test]
fn any_payload_decode_continues_the_outer_budget() {
let limit = LIMIT;
let chain: Vec<Frame> = (0..limit).map(|_| Frame::descriptor_proto()).collect();
to_json("google.protobuf.DescriptorProto", &encode_nested(&chain)).expect("standalone");
let mut wrapped = vec![Frame::any(DESCRIPTOR_PROTO_URL)];
wrapped.extend(chain);
assert_too_deep(to_json("google.protobuf.Any", &encode_nested(&wrapped)));
wrapped.pop();
to_json("google.protobuf.Any", &encode_nested(&wrapped)).expect("fits when one shallower");
}