use super::{
canonical, canonical_raw, decode, encode, read_fields,
serde::{Deserialize, Serialize},
ProtoFmt, ProtoRepr, Wire,
};
use prost::Message as _;
use prost_reflect::ReflectMessage;
use rand::{
distributions::{Distribution, Standard},
Rng,
};
use zksync_consensus_utils::EncodeDist;
#[track_caller]
pub fn test_encode<R: Rng, T: ProtoFmt + std::fmt::Debug + PartialEq>(rng: &mut R, x: &T) {
let x_encode = encode(x);
let x_canonical = canonical(x);
let x_shuffled = encode_shuffled(rng, x);
let desc = &<T as ProtoFmt>::Proto::default().descriptor();
for e in [&x_encode, &x_canonical, &x_shuffled] {
assert_eq!(x, &decode::<T>(e).unwrap());
assert_eq!(x_canonical, canonical_raw(e, desc).unwrap());
}
}
#[track_caller]
pub fn test_encode_random<T: ProtoFmt + std::fmt::Debug + PartialEq>(rng: &mut impl Rng)
where
Standard: Distribution<T>,
{
for _ in 0..10 {
let msg = rng.gen::<T>();
test_encode(rng, &msg);
}
}
fn encode_shuffled_raw<R: Rng>(
rng: &mut R,
buf: &[u8],
desc: &prost_reflect::MessageDescriptor,
) -> anyhow::Result<Vec<u8>> {
let mut fields: Vec<_> = read_fields(buf, desc)?.into_iter().collect();
for f in &mut fields {
f.1.reverse();
}
let mut v = vec![];
let mut w = quick_protobuf::Writer::new(&mut v);
while !fields.is_empty() {
let i = rng.gen_range(0..fields.len());
let Some(mut v) = fields[i].1.pop() else {
let last = fields.len() - 1;
fields.swap(i, last);
fields.pop();
continue;
};
let num = fields[i].0;
let fd = desc.get_field(num).unwrap();
if let prost_reflect::Kind::Message(desc) = &fd.kind() {
v = encode_shuffled_raw(rng, &v, desc)?;
}
let wire = Wire::from(fd.kind());
w.write_tag(num << 3 | wire.raw()).unwrap();
match wire {
Wire::Varint | Wire::I64 | Wire::I32 => {
for b in &v {
w.write_u8(*b).unwrap();
}
}
Wire::Len => w.write_bytes(&v).unwrap(),
}
}
Ok(v)
}
pub(crate) fn encode_shuffled<T: ProtoFmt, R: Rng>(rng: &mut R, x: &T) -> Vec<u8> {
let msg = x.build();
encode_shuffled_raw(rng, &msg.encode_to_vec(), &msg.descriptor()).unwrap()
}
pub trait ProtoConv {
type Type;
type Proto: ReflectMessage + Default;
fn read(r: &Self::Proto) -> anyhow::Result<Self::Type>;
fn build(this: &Self::Type) -> Self::Proto;
}
fn encode_proto<X: ProtoConv>(msg: &X::Type) -> Vec<u8> {
let msg = X::build(msg);
canonical_raw(&msg.encode_to_vec(), &msg.descriptor()).unwrap()
}
fn decode_proto<X: ProtoConv>(bytes: &[u8]) -> anyhow::Result<X::Type> {
X::read(&X::Proto::decode(bytes)?)
}
fn encode_json<X: ProtoConv>(msg: &X::Type) -> String {
let mut s = serde_json::Serializer::pretty(vec![]);
Serialize.proto(&X::build(msg), &mut s).unwrap();
String::from_utf8(s.into_inner()).unwrap()
}
fn decode_json<X: ProtoConv>(json: &str) -> anyhow::Result<X::Type> {
let mut d = serde_json::Deserializer::from_str(json);
X::read(
&Deserialize {
deny_unknown_fields: true,
}
.proto(&mut d)?,
)
}
fn encode_yaml<X: ProtoConv>(msg: &X::Type) -> String {
let mut s = serde_yaml::Serializer::new(vec![]);
Serialize.proto(&X::build(msg), &mut s).unwrap();
String::from_utf8(s.into_inner().unwrap()).unwrap()
}
fn decode_yaml<X: ProtoConv>(yaml: &str) -> anyhow::Result<X::Type> {
let d = serde_yaml::Deserializer::from_str(yaml);
X::read(
&Deserialize {
deny_unknown_fields: true,
}
.proto(d)?,
)
}
pub struct ReprConv<P: ProtoRepr>(std::marker::PhantomData<P>);
pub struct FmtConv<T: ProtoFmt>(std::marker::PhantomData<T>);
impl<T: ProtoFmt> ProtoConv for FmtConv<T> {
type Type = T;
type Proto = T::Proto;
fn read(r: &T::Proto) -> anyhow::Result<T> {
ProtoFmt::read(r)
}
fn build(this: &T) -> T::Proto {
ProtoFmt::build(this)
}
}
impl<P: ProtoRepr> ProtoConv for ReprConv<P> {
type Type = P::Type;
type Proto = P;
fn read(r: &P) -> anyhow::Result<P::Type> {
ProtoRepr::read(r)
}
fn build(this: &P::Type) -> P {
ProtoRepr::build(this)
}
}
#[track_caller]
pub fn test_encode_all_formats<X: ProtoConv>(rng: &mut impl Rng)
where
X::Type: std::fmt::Debug + PartialEq,
EncodeDist: Distribution<X::Type>,
{
for _ in 0..10 {
for required_only in [false, true] {
let want: X::Type = EncodeDist {
required_only,
decimal_fractions: false,
}
.sample(rng);
let got = decode_proto::<X>(&encode_proto::<X>(&want)).unwrap();
assert_eq!(&want, &got, "binary encoding");
let got = decode_yaml::<X>(&encode_yaml::<X>(&want)).unwrap();
assert_eq!(&want, &got, "yaml encoding");
let want: X::Type = EncodeDist {
required_only,
decimal_fractions: true,
}
.sample(rng);
let got = decode_json::<X>(&encode_json::<X>(&want)).unwrap();
assert_eq!(&want, &got, "json encoding");
}
}
}