#![cfg_attr(feature = "nightly", feature(likely_unlikely, allocator_api))]
#![cfg_attr(not(feature = "std"), no_std)]
pub mod arena;
pub(crate) mod base;
pub mod containers;
pub mod reflection;
pub use base::TypedMessage;
#[cfg(feature = "std")]
pub mod descriptor_pool;
#[doc(hidden)]
#[cfg(feature = "std")]
pub mod test_utils;
#[cfg(not(feature = "nightly"))]
pub use allocator_api2::alloc::Allocator;
#[cfg(feature = "nightly")]
pub use core::alloc::Allocator;
pub(crate) mod decoding;
pub(crate) mod encoding;
pub(crate) mod tables;
pub(crate) mod utils;
pub(crate) mod wire;
#[doc(hidden)]
pub mod generated_code_only;
use crate as protocrap;
include!("descriptor.pc.rs");
#[cfg(feature = "serde_support")]
pub mod serde;
#[cfg(feature = "serde_support")]
pub mod proto_json;
#[cfg(feature = "codegen")]
pub mod codegen;
#[derive(Debug)]
pub enum Error<E = ()> {
MessageTreeTooDeep,
BufferTooSmall,
InvalidProtobufData,
MessageNotFound,
ArenaAllocationFailed,
UnknownError,
Io(E),
}
impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}
impl<E: core::fmt::Debug> core::error::Error for Error<E> {}
impl<E> From<E> for Error<E> {
fn from(e: E) -> Self {
Error::Io(e)
}
}
pub trait ProtobufRef<'pool> {
fn as_dyn<'msg>(&'msg self) -> reflection::DynamicMessageRef<'pool, 'msg>;
fn descriptor(&self) -> &'pool crate::google::protobuf::DescriptorProto::ProtoType {
self.as_dyn().descriptor()
}
fn encode_flat<'a, const STACK_DEPTH: usize>(
&self,
buffer: &'a mut [u8],
) -> Result<&'a [u8], Error> {
let mut resumeable_encode = encoding::ResumeableEncode::<STACK_DEPTH>::new(self.as_dyn());
let encoding::ResumeResult::Done(buf) = resumeable_encode
.resume_encode(buffer)
.ok_or(Error::MessageTreeTooDeep)?
else {
return Err(Error::BufferTooSmall);
};
Ok(buf)
}
#[cfg(feature = "std")]
fn encode_vec<const STACK_DEPTH: usize>(&self) -> Result<Vec<u8>, Error> {
let mut buffer = vec![0u8; 1024];
let mut stack = Vec::new();
let mut resumeable_encode = encoding::ResumeableEncode::<STACK_DEPTH>::new(self.as_dyn());
loop {
match resumeable_encode
.resume_encode(&mut buffer)
.ok_or(Error::MessageTreeTooDeep)?
{
encoding::ResumeResult::Done(buf) => {
let len = buf.len();
let end = buffer.len();
let start = end - len;
buffer.copy_within(start..end, 0);
buffer.truncate(len);
break;
}
encoding::ResumeResult::NeedsMoreBuffer => {
let len = buffer.len().min(1024 * 1024);
stack.push(core::mem::take(&mut buffer));
buffer = vec![0u8; len * 2];
}
};
}
while let Some(old_buffer) = stack.pop() {
buffer.extend_from_slice(&old_buffer);
}
Ok(buffer)
}
}
pub trait ProtobufMut<'pool>: ProtobufRef<'pool> {
fn as_dyn_mut<'msg>(&'msg mut self) -> reflection::DynamicMessage<'pool, 'msg>;
#[must_use]
fn decode_flat<const STACK_DEPTH: usize>(
&mut self,
arena: &mut crate::arena::Arena,
buf: &[u8],
) -> bool {
let mut decoder =
decoding::ResumeableDecode::<STACK_DEPTH>::new(self.as_dyn_mut(), isize::MAX);
if !decoder.resume(buf, arena) {
self.as_dyn_mut().clear();
return false;
}
if !decoder.finish(arena) {
self.as_dyn_mut().clear();
return false;
}
true
}
fn decode<'a, E>(
&mut self,
arena: &mut crate::arena::Arena,
provider: &'a mut impl FnMut() -> Result<Option<&'a [u8]>, E>,
) -> Result<(), Error<E>> {
let mut decoder = decoding::ResumeableDecode::<32>::new(self.as_dyn_mut(), isize::MAX);
loop {
let Some(buffer) = provider().map_err(Error::Io)? else {
break;
};
if !decoder.resume(buffer, arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
}
if !decoder.finish(arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
Ok(())
}
fn async_decode<'a, E, F>(
&'a mut self,
arena: &mut crate::arena::Arena,
provider: &'a mut impl FnMut() -> F,
) -> impl core::future::Future<Output = Result<(), Error<E>>>
where
F: core::future::Future<Output = Result<Option<&'a [u8]>, E>> + 'a,
{
async move {
let mut decoder = decoding::ResumeableDecode::<32>::new(self.as_dyn_mut(), isize::MAX);
loop {
let Some(buffer) = provider().await.map_err(Error::Io)? else {
break;
};
if !decoder.resume(buffer, arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
}
if !decoder.finish(arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
Ok(())
}
}
#[cfg(feature = "std")]
fn decode_from_bufread<const STACK_DEPTH: usize>(
&mut self,
arena: &mut crate::arena::Arena,
reader: &mut impl std::io::BufRead,
) -> Result<(), Error<std::io::Error>> {
let mut decoder =
decoding::ResumeableDecode::<STACK_DEPTH>::new(self.as_dyn_mut(), isize::MAX);
loop {
let buffer = reader.fill_buf().map_err(Error::Io)?;
let len = buffer.len();
if len == 0 {
break;
}
if !decoder.resume(buffer, arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
reader.consume(len);
}
if !decoder.finish(arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
Ok(())
}
#[cfg(feature = "std")]
fn decode_from_read<const STACK_DEPTH: usize>(
&mut self,
arena: &mut crate::arena::Arena,
reader: &mut impl std::io::Read,
) -> Result<(), Error<std::io::Error>> {
let mut buf_reader = std::io::BufReader::new(reader);
self.decode_from_bufread::<STACK_DEPTH>(arena, &mut buf_reader)
}
#[cfg(feature = "std")]
fn decode_from_async_bufread<'a, const STACK_DEPTH: usize>(
&'a mut self,
arena: &'a mut crate::arena::Arena<'a>,
reader: &mut (impl futures::io::AsyncBufRead + Unpin),
) -> impl core::future::Future<Output = Result<(), Error<futures::io::Error>>> {
use futures::io::AsyncBufReadExt;
async move {
let mut decoder =
decoding::ResumeableDecode::<STACK_DEPTH>::new(self.as_dyn_mut(), isize::MAX);
loop {
let buffer = reader.fill_buf().await.map_err(Error::Io)?;
let len = buffer.len();
if len == 0 {
break;
}
if !decoder.resume(buffer, arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
reader.consume_unpin(len);
}
if !decoder.finish(arena) {
self.as_dyn_mut().clear();
return Err(Error::InvalidProtobufData);
}
Ok(())
}
}
#[cfg(feature = "std")]
fn decode_from_async_read<'a, const STACK_DEPTH: usize>(
&'a mut self,
arena: &'a mut crate::arena::Arena<'a>,
reader: &mut (impl futures::io::AsyncRead + Unpin),
) -> impl core::future::Future<Output = Result<(), Error<futures::io::Error>>> {
async move {
let mut buf_reader = futures::io::BufReader::new(reader);
self.decode_from_async_bufread::<STACK_DEPTH>(arena, &mut buf_reader)
.await
}
}
#[cfg(feature = "serde_support")]
fn serde_deserialize<'arena, 'alloc, 'de, D>(
&'de mut self,
arena: &'arena mut crate::arena::Arena<'alloc>,
deserializer: D,
) -> Result<(), D::Error>
where
D: ::serde::Deserializer<'de>,
{
serde::serde_deserialize_struct(self.as_dyn_mut(), arena, deserializer)
}
}
impl<T: generated_code_only::Protobuf> ProtobufRef<'static> for T {
fn as_dyn<'msg>(&'msg self) -> reflection::DynamicMessageRef<'static, 'msg> {
reflection::DynamicMessageRef {
object: crate::generated_code_only::as_object(self),
table: T::table(),
}
}
}
impl<T: generated_code_only::Protobuf> ProtobufMut<'static> for T {
fn as_dyn_mut<'msg>(&'msg mut self) -> reflection::DynamicMessage<'static, 'msg> {
reflection::DynamicMessage {
object: crate::generated_code_only::as_object_mut(self),
table: T::table(),
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use crate::ProtobufMut;
use crate::ProtobufRef;
#[cfg(not(feature = "nightly"))]
use allocator_api2::alloc::Global;
#[cfg(feature = "nightly")]
use std::alloc::Global;
#[test]
fn descriptor_accessors() {
let file_descriptor =
crate::google::protobuf::FileDescriptorProto::ProtoType::file_descriptor();
let message_descriptor =
crate::google::protobuf::DescriptorProto::ProtoType::descriptor_proto();
let nested_descriptor =
crate::google::protobuf::DescriptorProto::ExtensionRange::ProtoType::descriptor_proto();
assert_eq!(file_descriptor.name(), "google/protobuf/descriptor.proto");
assert_eq!(message_descriptor.name(), "DescriptorProto");
assert_eq!(nested_descriptor.name(), "ExtensionRange");
}
#[test]
fn file_descriptor_roundtrip() {
crate::test_utils::assert_roundtrip(
crate::google::protobuf::FileDescriptorProto::ProtoType::file_descriptor(),
);
}
#[test]
fn compare_encode_flat_vs_vec() {
let file_descriptor =
crate::google::protobuf::FileDescriptorProto::ProtoType::file_descriptor();
let mut flat_buffer = vec![0u8; 100_000];
let flat_result = file_descriptor
.encode_flat::<32>(&mut flat_buffer)
.expect("encode_flat should work");
let flat_bytes = flat_result.to_vec();
let vec_bytes = file_descriptor
.encode_vec::<32>()
.expect("encode_vec should work");
std::fs::write("/tmp/encode_flat.bin", &flat_bytes).expect("write flat");
std::fs::write("/tmp/encode_vec.bin", &vec_bytes).expect("write vec");
println!(
"encode_flat: {} bytes, encode_vec: {} bytes",
flat_bytes.len(),
vec_bytes.len()
);
if flat_bytes != vec_bytes {
for (i, (a, b)) in flat_bytes.iter().zip(vec_bytes.iter()).enumerate() {
if a != b {
println!(
"First difference at byte {}: flat={:02x}, vec={:02x}",
i, a, b
);
break;
}
}
if flat_bytes.len() != vec_bytes.len() {
println!(
"Length mismatch: flat={}, vec={}",
flat_bytes.len(),
vec_bytes.len()
);
}
}
assert_eq!(
flat_bytes, vec_bytes,
"encode_flat and encode_vec should produce identical output"
);
}
#[test]
fn dynamic_file_descriptor_roundtrip() {
let mut pool = crate::descriptor_pool::DescriptorPool::new(&Global);
let file_descriptor =
crate::google::protobuf::FileDescriptorProto::ProtoType::file_descriptor();
pool.add_file(&file_descriptor).unwrap();
let bytes = file_descriptor.encode_vec::<32>().expect("should encode");
let mut arena = crate::arena::Arena::new(&Global);
let mut dynamic_file_descriptor = pool
.create_message("google.protobuf.FileDescriptorProto", &mut arena)
.expect("Should create");
assert!(dynamic_file_descriptor.decode_flat::<32>(&mut arena, &bytes));
let roundtrip = dynamic_file_descriptor
.encode_vec::<32>()
.expect("should encode");
assert_eq!(bytes, roundtrip);
}
#[test]
fn invalid_utf8_string_rejected() {
let invalid_utf8_name: &[u8] = &[0x0a, 0x03, 0x61, 0xFF, 0x62];
let mut arena = crate::arena::Arena::new(&Global);
let mut msg = crate::google::protobuf::FileDescriptorProto::ProtoType::default();
let result = msg.decode_flat::<32>(&mut arena, invalid_utf8_name);
assert!(
!result,
"decoding invalid UTF-8 in string field should fail"
);
}
}