use std::collections::HashMap;
use std::sync::Arc;
use prost_reflect::MessageDescriptor;
use super::super::{enter_level, render_message, FieldOrExt, ANNOTATIONS, CBL_START, EXPAND_ANY};
use super::annotations::{push_tag_modifiers, AnnWriter};
use super::output::{push_indent, wob_prefix_n, write_close_brace};
use crate::helpers::{
parse_varint, parse_wiretag, WT_I32, WT_I64, WT_LEN, WT_START_GROUP, WT_VARINT,
};
use crate::serialize::common::escape_string_into;
struct AnyFields<'a> {
type_url: &'a str,
value: Option<&'a [u8]>,
}
fn scan_any_fields(data: &[u8]) -> Option<AnyFields<'_>> {
let mut pos = 0;
let buflen = data.len();
let mut type_url: Option<&str> = None;
let mut value: Option<&[u8]> = None;
let mut value_before_type_url = false;
while pos < buflen {
let tag = parse_wiretag(data, pos);
if tag.wtag_gar.is_some() {
return None;
}
let field_number = tag.wfield.unwrap();
let wire_type = tag.wtype.unwrap();
pos = tag.next_pos;
match wire_type {
WT_VARINT => {
let vr = parse_varint(data, pos);
if vr.varint_gar.is_some() {
return None;
}
pos = vr.next_pos;
}
WT_I64 => {
if pos + 8 > buflen {
return None;
}
pos += 8;
}
WT_LEN => {
let lr = parse_varint(data, pos);
if lr.varint_gar.is_some() {
return None;
}
pos = lr.next_pos;
let len = lr.varint.unwrap() as usize;
if pos + len > buflen {
return None;
}
let payload = &data[pos..pos + len];
pos += len;
match field_number {
1 => {
let s = std::str::from_utf8(payload).ok()?;
if s.is_empty() {
return None;
}
type_url = Some(s);
}
2 => {
if type_url.is_none() {
value_before_type_url = true;
}
value = Some(payload);
}
_ => {}
}
}
WT_START_GROUP => {
pos = skip_group(data, pos, field_number)?;
}
WT_I32 => {
if pos + 4 > buflen {
return None;
}
pos += 4;
}
_ => return None,
}
}
if value_before_type_url {
return None;
}
let type_url = type_url?;
Some(AnyFields { type_url, value })
}
fn skip_group(buf: &[u8], mut pos: usize, expected_field: u64) -> Option<usize> {
let buflen = buf.len();
loop {
if pos == buflen {
return None;
}
let tag = parse_wiretag(buf, pos);
if tag.wtag_gar.is_some() {
return None;
}
let field_number = tag.wfield.unwrap();
let wire_type = tag.wtype.unwrap();
pos = tag.next_pos;
match wire_type {
WT_VARINT => {
let vr = parse_varint(buf, pos);
if vr.varint_gar.is_some() {
return None;
}
pos = vr.next_pos;
}
WT_I64 => {
if pos + 8 > buflen {
return None;
}
pos += 8;
}
WT_LEN => {
let lr = parse_varint(buf, pos);
if lr.varint_gar.is_some() {
return None;
}
pos = lr.next_pos;
let len = lr.varint.unwrap() as usize;
if pos + len > buflen {
return None;
}
pos += len;
}
WT_START_GROUP => {
pos = skip_group(buf, pos, field_number)?;
}
4 => {
if field_number != expected_field {
return None;
}
return Some(pos);
}
WT_I32 => {
if pos + 4 > buflen {
return None;
}
pos += 4;
}
_ => return None,
}
}
}
#[allow(clippy::too_many_arguments)]
pub(in super::super) fn render_any_expansion(
field_number: u64,
fs: &FieldOrExt,
all_schemas: Option<&HashMap<String, Arc<MessageDescriptor>>>,
tag_ohb: Option<u64>,
tag_oor: bool,
len_ohb: Option<u64>,
data: &[u8],
out: &mut Vec<u8>,
) -> bool {
if !EXPAND_ANY.with(|c| c.get()) {
return false;
}
let fields = match scan_any_fields(data) {
Some(f) => f,
None => return false,
};
let fqdn = if let Some(slash) = fields.type_url.rfind('/') {
&fields.type_url[slash + 1..]
} else {
fields.type_url
};
let resolved_desc: Option<Arc<MessageDescriptor>> =
all_schemas.and_then(|m| m.get(fqdn)).cloned();
let resolved_desc = match resolved_desc {
Some(d) => d,
None => return false,
};
let annotations = ANNOTATIONS.with(|c| c.get());
wob_prefix_n(field_number, Some(fs), false, out);
if annotations {
let mut aw = AnnWriter::new();
aw.push_field_decl(out, field_number, Some(fs), None, None);
push_tag_modifiers(&mut aw, out, tag_ohb, tag_oor, len_ohb);
}
out.push(b'\n');
CBL_START.with(|c| c.set(out.len()));
let outer_guard = enter_level();
push_indent(out);
out.extend_from_slice(b"type_url: \"");
escape_string_into(fields.type_url, out);
out.push(b'"');
if annotations {
let mut aw = AnnWriter::new();
aw.push(out, b"string = 1");
}
out.push(b'\n');
CBL_START.with(|c| c.set(out.len()));
push_indent(out);
out.extend_from_slice(b"value {");
if annotations {
let mut aw = AnnWriter::new();
aw.sep(out);
out.extend_from_slice(resolved_desc.name().as_bytes());
out.extend_from_slice(b" = 2");
}
out.push(b'\n');
CBL_START.with(|c| c.set(out.len()));
{
let inner_guard = enter_level();
let value_bytes: &[u8] = fields.value.unwrap_or(&[]);
render_message(
value_bytes,
0,
None,
Some(&*resolved_desc),
all_schemas,
out,
);
drop(inner_guard); }
write_close_brace(out);
drop(outer_guard);
write_close_brace(out);
true
}