From 6910f11d6982198c02e05c7521f01e9e50f3286b Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 18 May 2026 08:43:22 -0700 Subject: [PATCH] Add no_std test example and update alloc gating Update codegen to make `bytes` imports conditional on the `alloc` feature and add a test example for the `thumbv7em-none-eabihf` target. --- Cargo.lock | 25 + codegen/src/generator.rs | 18 +- examples/no_std_test/.cargo/config.toml | 2 + examples/no_std_test/Cargo.toml | 5 + examples/no_std_test/src/helloworld.rs | 677 ++++++++++++++++++++++++ examples/no_std_test/src/main.rs | 50 +- personas/researcher.md | 2 +- roto-tonic/Cargo.toml | 1 + roto-tonic/src/generated/interop.rs | 15 +- 9 files changed, 770 insertions(+), 25 deletions(-) create mode 100644 examples/no_std_test/.cargo/config.toml create mode 100644 examples/no_std_test/src/helloworld.rs diff --git a/Cargo.lock b/Cargo.lock index ac9311d..6f911fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,12 @@ dependencies = [ "itertools", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -347,6 +353,16 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embedded-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + [[package]] name = "env_filter" version = "1.0.1" @@ -776,6 +792,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -836,6 +858,8 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" name = "no_std_test" version = "0.1.0" dependencies = [ + "bytes", + "embedded-alloc", "roto-runtime", ] @@ -1201,6 +1225,7 @@ dependencies = [ name = "roto-tonic" version = "0.1.0" dependencies = [ + "async-trait", "bytes", "futures-util", "http", diff --git a/codegen/src/generator.rs b/codegen/src/generator.rs index 4522635..ffdcec9 100644 --- a/codegen/src/generator.rs +++ b/codegen/src/generator.rs @@ -6,7 +6,7 @@ use roto_runtime::ProtoAccessor; use std::collections::{HashMap, HashSet}; use std::str; -const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n"; +const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\n#[cfg(feature = \"alloc\")]\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n"; const SERVICE_IMPORTS: &str = "use tonic::{Request, Response, Status};\nuse tokio_stream::Stream;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::{Context, Poll};\nuse std::future::Future;\nuse tonic::body::BoxBody;\nuse tower::Service;\nuse futures_util::StreamExt;\nuse http_body_util::BodyExt;\nuse http_body::Body;\nuse crate::{BufferPool, StatusBody};\n"; pub fn to_pascal_case(s: &str) -> String { @@ -54,7 +54,7 @@ fn map_type_to_rust_accessor(field_type: i32, label: i32, is_map: bool) -> (Stri match field_type { 9 => ( "&'a str".to_string(), - "std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(), + "core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(), "\"\"".to_string(), ), // TYPE_STRING 1 => ( @@ -665,7 +665,7 @@ pub fn generate_service_code( set, files_to_generate, generate_mod_files, - SERVICE_IMPORTS, + "", |file_proto, output| { let package = file_proto.package().unwrap_or("").to_string(); // Services @@ -727,21 +727,13 @@ pub fn generate_rust_code( } fn strip_boilerplate(content: &str) -> String { - // Find the first occurrence of a service definition or a trait - // In our case, the services start after the dependency imports and a newline. - if let Some(idx) = content.find("pub trait ") { - return content[idx..].to_string(); - } - if let Some(idx) = content.find("pub struct ") { - // This might be a message, but generate_service_code only generates services (and their server structs) - return content[idx..].to_string(); - } content.to_string() } fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut String) { + output.push_str(SERVICE_IMPORTS); let svc_name = to_pascal_case(svc_proto.name().unwrap()); - output.push_str(&format!("#[tonic::async_trait]\npub trait {}: Send + Sync + 'static {{\n", svc_name)); + output.push_str(&format!("#[async_trait::async_trait]\npub trait {}: Send + Sync + 'static {{\n", svc_name)); for method_res in svc_proto.method() { let (method_data, _) = method_res.expect("Failed to iterate method"); diff --git a/examples/no_std_test/.cargo/config.toml b/examples/no_std_test/.cargo/config.toml new file mode 100644 index 0000000..5d10902 --- /dev/null +++ b/examples/no_std_test/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "thumbv7em-none-eabihf" diff --git a/examples/no_std_test/Cargo.toml b/examples/no_std_test/Cargo.toml index 84cfd5b..563b27e 100644 --- a/examples/no_std_test/Cargo.toml +++ b/examples/no_std_test/Cargo.toml @@ -5,6 +5,11 @@ edition = "2024" [dependencies] roto-runtime = { path = "../../runtime", default-features = false } +embedded-alloc = { version = "0.5", optional = true } +bytes = { version = "1.7", default-features = false } + +[features] +alloc = ["roto-runtime/alloc", "embedded-alloc"] [profile.dev] panic = "abort" diff --git a/examples/no_std_test/src/helloworld.rs b/examples/no_std_test/src/helloworld.rs new file mode 100644 index 0000000..1a7e2a7 --- /dev/null +++ b/examples/no_std_test/src/helloworld.rs @@ -0,0 +1,677 @@ +// @generated by protoc-gen-roto — do not edit +#[allow(unused_imports)] + +use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage}; +use core::str; +use bytes::{Bytes, BytesMut, Buf, BufMut}; + +pub struct Hello<'a> { + accessor: roto_runtime::ProtoAccessor<'a>, + name_offset: Option, + d_offset: Option, + f_offset: Option, + b_offset: Option, + n_offset: Option, + l_offset: Option, + c1_offset: Option, + c2_offset: Option, + pets_start: Option, + pets_end: Option, +} + +impl<'a> Hello<'a> { + pub fn new(data: &'a [u8]) -> roto_runtime::Result { + let accessor = roto_runtime::ProtoAccessor::new(data)?; + let mut name_offset = None; + let mut d_offset = None; + let mut f_offset = None; + let mut b_offset = None; + let mut n_offset = None; + let mut l_offset = None; + let mut c1_offset = None; + let mut c2_offset = None; + let mut pets_start = None; + let mut pets_end = None; + for item in accessor.fields() { + let (offset, tag, _) = item?; + if tag.field_number == 1 { name_offset = Some(offset); } + if tag.field_number == 2 { d_offset = Some(offset); } + if tag.field_number == 3 { f_offset = Some(offset); } + if tag.field_number == 4 { b_offset = Some(offset); } + if tag.field_number == 5 { n_offset = Some(offset); } + if tag.field_number == 6 { l_offset = Some(offset); } + if tag.field_number == 7 { c1_offset = Some(offset); } + if tag.field_number == 8 { c2_offset = Some(offset); } + if tag.field_number == 9 { + if pets_start.is_none() { pets_start = Some(offset); } + pets_end = Some(offset); + } + } + + Ok(Self { + accessor, +name_offset, +d_offset, +f_offset, +b_offset, +n_offset, +l_offset, +c1_offset, +c2_offset, +pets_start, pets_end, + }) + } + + pub fn name(&self) -> roto_runtime::Result<&'a str> { + let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> { + self.name().or(Ok("")) + } + + pub fn has_name(&self) -> bool { self.name_offset.is_some() } + + pub fn d(&self) -> roto_runtime::Result { + let offset = self.d_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?)) + } + + pub fn d_or_default(&self) -> roto_runtime::Result { + self.d().or(Ok(0.0)) + } + + pub fn has_d(&self) -> bool { self.d_offset.is_some() } + + pub fn f(&self) -> roto_runtime::Result { + let offset = self.f_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?)) + } + + pub fn f_or_default(&self) -> roto_runtime::Result { + self.f().or(Ok(0.0)) + } + + pub fn has_f(&self) -> bool { self.f_offset.is_some() } + + pub fn b(&self) -> roto_runtime::Result { + let offset = self.b_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn b_or_default(&self) -> roto_runtime::Result { + self.b().or(Ok(false)) + } + + pub fn has_b(&self) -> bool { self.b_offset.is_some() } + + pub fn n(&self) -> roto_runtime::Result { + let offset = self.n_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn n_or_default(&self) -> roto_runtime::Result { + self.n().or(Ok(0)) + } + + pub fn has_n(&self) -> bool { self.n_offset.is_some() } + + pub fn l(&self) -> roto_runtime::Result { + let offset = self.l_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn l_or_default(&self) -> roto_runtime::Result { + self.l().or(Ok(0)) + } + + pub fn has_l(&self) -> bool { self.l_offset.is_some() } + + pub fn c1(&self) -> roto_runtime::Result<&'a str> { + let offset = self.c1_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn c1_or_default(&self) -> roto_runtime::Result<&'a str> { + self.c1().or(Ok("")) + } + + pub fn has_c1(&self) -> bool { self.c1_offset.is_some() } + + pub fn c2(&self) -> roto_runtime::Result { + let offset = self.c2_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn c2_or_default(&self) -> roto_runtime::Result { + self.c2().or(Ok(false)) + } + + pub fn has_c2(&self) -> bool { self.c2_offset.is_some() } + + pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> { + match (self.pets_start, self.pets_end) { + (Some(start), Some(end)) => self.accessor.iter_repeated_range(9, start, end), + _ => self.accessor.iter_repeated(9), + } + } + + pub fn which_choice(&self) -> roto_runtime::Result> > { + if self.c1_offset.is_some() { + return Ok(Some(hello::Choice::c1 (self.c1()?))); + } + if self.c2_offset.is_some() { + return Ok(Some(hello::Choice::c2 (self.c2()?))); + } + Ok(None) + } + + pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> { + self.accessor.raw_fields() + } + +} + +pub struct HelloBuilder<'b> { + builder: roto_runtime::ProtoBuilder<'b>, + name_written: bool, + d_written: bool, + f_written: bool, + b_written: bool, + n_written: bool, + l_written: bool, + c1_written: bool, + c2_written: bool, + pets_written: bool, +} + +impl<'b> HelloBuilder<'b> { + pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> { + HelloBuilder { + builder: roto_runtime::ProtoBuilder::new(buf), + name_written: false, + d_written: false, + f_written: false, + b_written: false, + n_written: false, + l_written: false, + c1_written: false, + c2_written: false, + pets_written: false, + } + } + + pub fn name(mut self, value: &str) -> roto_runtime::Result { + self.builder.write_string(1, value)?; + self.name_written = true; + Ok(self) + } + + pub fn d(mut self, value: &[u8]) -> roto_runtime::Result { + self.builder.write_bytes(2, value)?; + self.d_written = true; + Ok(self) + } + + pub fn f(mut self, value: &[u8]) -> roto_runtime::Result { + self.builder.write_bytes(3, value)?; + self.f_written = true; + Ok(self) + } + + pub fn b(mut self, value: u64) -> roto_runtime::Result { + self.builder.write_varint(4, value)?; + self.b_written = true; + Ok(self) + } + + pub fn n(mut self, value: i32) -> roto_runtime::Result { + self.builder.write_int32(5, value)?; + self.n_written = true; + Ok(self) + } + + pub fn l(mut self, value: u64) -> roto_runtime::Result { + self.builder.write_varint(6, value)?; + self.l_written = true; + Ok(self) + } + + pub fn c1(mut self, value: &str) -> roto_runtime::Result { + self.builder.write_string(7, value)?; + self.c1_written = true; + Ok(self) + } + + pub fn c2(mut self, value: u64) -> roto_runtime::Result { + self.builder.write_varint(8, value)?; + self.c2_written = true; + Ok(self) + } + + pub fn pets(mut self, value: &[u8]) -> roto_runtime::Result { + self.builder.write_bytes(9, value)?; + self.pets_written = true; + Ok(self) + } + + pub fn with(mut self, msg: &Hello<'_>) -> roto_runtime::Result { + for item in msg.accessor.raw_fields() { + let (field_number, raw_bytes) = item?; + let is_written = match field_number { + 1 => self.name_written, + 2 => self.d_written, + 3 => self.f_written, + 4 => self.b_written, + 5 => self.n_written, + 6 => self.l_written, + 7 => self.c1_written, + 8 => self.c2_written, + 9 => self.pets_written, + _ => false, + }; + if !is_written { + self.builder.write_raw(raw_bytes)?; + } + } + Ok(self) + } + + pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> { + self.builder.finish() + } +} + +#[cfg(feature = "alloc")] +pub struct OwnedHello { + pub data: bytes::Bytes, +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoOwned for OwnedHello { + type Reader<'a> = Hello<'a>; + fn reader(&self) -> Hello<'_> { + Hello::new(&self.data).expect("failed to create reader") + } +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoMessage for OwnedHello { + fn decode(buf: bytes::Bytes) -> roto_runtime::Result { + Ok(OwnedHello { data: buf }) + } + + fn bytes(&self) -> bytes::Bytes { + self.data.clone() + } +} + +pub mod hello { +pub struct Pet<'a> { + accessor: roto_runtime::ProtoAccessor<'a>, + name_offset: Option, + color_offset: Option, +} + +impl<'a> Pet<'a> { + pub fn new(data: &'a [u8]) -> roto_runtime::Result { + let accessor = roto_runtime::ProtoAccessor::new(data)?; + let mut name_offset = None; + let mut color_offset = None; + for item in accessor.fields() { + let (offset, tag, _) = item?; + if tag.field_number == 1 { name_offset = Some(offset); } + if tag.field_number == 2 { color_offset = Some(offset); } + } + + Ok(Self { + accessor, +name_offset, +color_offset, + }) + } + + pub fn name(&self) -> roto_runtime::Result<&'a str> { + let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> { + self.name().or(Ok("")) + } + + pub fn has_name(&self) -> bool { self.name_offset.is_some() } + + pub fn color(&self) -> roto_runtime::Result { + let offset = self.color_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation) + } + + pub fn color_or_default(&self) -> roto_runtime::Result { + self.color().or(Ok(0)) + } + + pub fn has_color(&self) -> bool { self.color_offset.is_some() } + + pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> { + self.accessor.raw_fields() + } + +} + +pub struct PetBuilder<'b> { + builder: roto_runtime::ProtoBuilder<'b>, + name_written: bool, + color_written: bool, +} + +impl<'b> PetBuilder<'b> { + pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> { + PetBuilder { + builder: roto_runtime::ProtoBuilder::new(buf), + name_written: false, + color_written: false, + } + } + + pub fn name(mut self, value: &str) -> roto_runtime::Result { + self.builder.write_string(1, value)?; + self.name_written = true; + Ok(self) + } + + pub fn color(mut self, value: u64) -> roto_runtime::Result { + self.builder.write_varint(2, value)?; + self.color_written = true; + Ok(self) + } + + pub fn with(mut self, msg: &Pet<'_>) -> roto_runtime::Result { + for item in msg.accessor.raw_fields() { + let (field_number, raw_bytes) = item?; + let is_written = match field_number { + 1 => self.name_written, + 2 => self.color_written, + _ => false, + }; + if !is_written { + self.builder.write_raw(raw_bytes)?; + } + } + Ok(self) + } + + pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> { + self.builder.finish() + } +} + +#[cfg(feature = "alloc")] +pub struct OwnedPet { + pub data: bytes::Bytes, +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoOwned for OwnedPet { + type Reader<'a> = Pet<'a>; + fn reader(&self) -> Pet<'_> { + Pet::new(&self.data).expect("failed to create reader") + } +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoMessage for OwnedPet { + fn decode(buf: bytes::Bytes) -> roto_runtime::Result { + Ok(OwnedPet { data: buf }) + } + + fn bytes(&self) -> bytes::Bytes { + self.data.clone() + } +} + +pub mod pet { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum Color { + BLACK = 0, + WHITE = 1, + BLUE = 2, + RED = 3, + YELLOW = 4, + GREEN = 5, +} + +impl Color { + pub fn from_i32(value: i32) -> Self { + match value { + 0 => Color::BLACK, + 1 => Color::WHITE, + 2 => Color::BLUE, + 3 => Color::RED, + 4 => Color::YELLOW, + 5 => Color::GREEN, + _ => Color::BLACK, + } + } +} + +} + +pub enum Choice<'a> { + c1(&'a str), + c2(bool), +} + +} + +pub struct HelloRequest<'a> { + accessor: roto_runtime::ProtoAccessor<'a>, + request_offset: Option, +} + +impl<'a> HelloRequest<'a> { + pub fn new(data: &'a [u8]) -> roto_runtime::Result { + let accessor = roto_runtime::ProtoAccessor::new(data)?; + let mut request_offset = None; + for item in accessor.fields() { + let (offset, tag, _) = item?; + if tag.field_number == 1 { request_offset = Some(offset); } + } + + Ok(Self { + accessor, +request_offset, + }) + } + + pub fn request(&self) -> roto_runtime::Result<&'a [u8]> { + let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + Ok(bytes) + } + + pub fn request_or_default(&self) -> roto_runtime::Result<&'a [u8]> { + self.request().or(Ok(&[])) + } + + pub fn has_request(&self) -> bool { self.request_offset.is_some() } + + pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> { + self.accessor.raw_fields() + } + +} + +pub struct HelloRequestBuilder<'b> { + builder: roto_runtime::ProtoBuilder<'b>, + request_written: bool, +} + +impl<'b> HelloRequestBuilder<'b> { + pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> { + HelloRequestBuilder { + builder: roto_runtime::ProtoBuilder::new(buf), + request_written: false, + } + } + + pub fn request(mut self, value: &[u8]) -> roto_runtime::Result { + self.builder.write_bytes(1, value)?; + self.request_written = true; + Ok(self) + } + + pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result { + for item in msg.accessor.raw_fields() { + let (field_number, raw_bytes) = item?; + let is_written = match field_number { + 1 => self.request_written, + _ => false, + }; + if !is_written { + self.builder.write_raw(raw_bytes)?; + } + } + Ok(self) + } + + pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> { + self.builder.finish() + } +} + +#[cfg(feature = "alloc")] +pub struct OwnedHelloRequest { + pub data: bytes::Bytes, +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoOwned for OwnedHelloRequest { + type Reader<'a> = HelloRequest<'a>; + fn reader(&self) -> HelloRequest<'_> { + HelloRequest::new(&self.data).expect("failed to create reader") + } +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoMessage for OwnedHelloRequest { + fn decode(buf: bytes::Bytes) -> roto_runtime::Result { + Ok(OwnedHelloRequest { data: buf }) + } + + fn bytes(&self) -> bytes::Bytes { + self.data.clone() + } +} + +pub struct HelloReply<'a> { + accessor: roto_runtime::ProtoAccessor<'a>, + response_offset: Option, +} + +impl<'a> HelloReply<'a> { + pub fn new(data: &'a [u8]) -> roto_runtime::Result { + let accessor = roto_runtime::ProtoAccessor::new(data)?; + let mut response_offset = None; + for item in accessor.fields() { + let (offset, tag, _) = item?; + if tag.field_number == 1 { response_offset = Some(offset); } + } + + Ok(Self { + accessor, +response_offset, + }) + } + + pub fn response(&self) -> roto_runtime::Result<&'a [u8]> { + let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; + let (bytes, _) = self.accessor.get_value_at(offset)?; + Ok(bytes) + } + + pub fn response_or_default(&self) -> roto_runtime::Result<&'a [u8]> { + self.response().or(Ok(&[])) + } + + pub fn has_response(&self) -> bool { self.response_offset.is_some() } + + pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> { + self.accessor.raw_fields() + } + +} + +pub struct HelloReplyBuilder<'b> { + builder: roto_runtime::ProtoBuilder<'b>, + response_written: bool, +} + +impl<'b> HelloReplyBuilder<'b> { + pub fn builder(buf: &mut [u8]) -> HelloReplyBuilder<'_> { + HelloReplyBuilder { + builder: roto_runtime::ProtoBuilder::new(buf), + response_written: false, + } + } + + pub fn response(mut self, value: &[u8]) -> roto_runtime::Result { + self.builder.write_bytes(1, value)?; + self.response_written = true; + Ok(self) + } + + pub fn with(mut self, msg: &HelloReply<'_>) -> roto_runtime::Result { + for item in msg.accessor.raw_fields() { + let (field_number, raw_bytes) = item?; + let is_written = match field_number { + 1 => self.response_written, + _ => false, + }; + if !is_written { + self.builder.write_raw(raw_bytes)?; + } + } + Ok(self) + } + + pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> { + self.builder.finish() + } +} + +#[cfg(feature = "alloc")] +pub struct OwnedHelloReply { + pub data: bytes::Bytes, +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoOwned for OwnedHelloReply { + type Reader<'a> = HelloReply<'a>; + fn reader(&self) -> HelloReply<'_> { + HelloReply::new(&self.data).expect("failed to create reader") + } +} + +#[cfg(feature = "alloc")] +impl roto_runtime::RotoMessage for OwnedHelloReply { + fn decode(buf: bytes::Bytes) -> roto_runtime::Result { + Ok(OwnedHelloReply { data: buf }) + } + + fn bytes(&self) -> bytes::Bytes { + self.data.clone() + } +} diff --git a/examples/no_std_test/src/main.rs b/examples/no_std_test/src/main.rs index 14f006a..1cf9cf9 100644 --- a/examples/no_std_test/src/main.rs +++ b/examples/no_std_test/src/main.rs @@ -1,16 +1,60 @@ #![no_std] #![no_main] -use roto_runtime::ProtoAccessor; +mod helloworld; + +#[cfg(feature = "alloc")] +extern crate alloc; + +use roto_runtime::{ProtoAccessor, RotoMessage, RotoOwned}; + +#[cfg(feature = "alloc")] +#[global_allocator] +static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty(); #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } +#[cfg(feature = "alloc")] +#[unsafe(no_mangle)] +pub extern "C" fn _critical_section_1_0_acquire() {} + +#[cfg(feature = "alloc")] +#[unsafe(no_mangle)] +pub extern "C" fn _critical_section_1_0_release() {} + +static HELLO_DATA: &[u8] = &[0x0A, 0x05, 0x57, 0x6f, 0x72, 0x6c, 0x64]; + #[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { - let _data = [0u8; 0]; - let _ = ProtoAccessor::new(&_data); + #[cfg(not(feature = "alloc"))] + { + let hello = helloworld::Hello::new(HELLO_DATA).expect("failed to decode hello"); + let _name = hello.name().expect("failed to get name"); + if !_name.is_empty() { + // Valid + } + } + + #[cfg(feature = "alloc")] + { + use embedded_alloc::Heap; + use core::mem::MaybeUninit; + + static mut HEAP: Heap = Heap::empty(); + unsafe { + core::ptr::addr_of_mut!(HEAP).write(embedded_alloc::Heap::empty()); + (*core::ptr::addr_of_mut!(HEAP)).init(MaybeUninit::::uninit().as_ptr() as *mut u8 as usize, 1024 * 1024); + let owned_hello = helloworld::OwnedHello::decode(HELLO_DATA.into()).expect("failed to decode owned hello"); + let hello_reader = owned_hello.reader(); + let _name = hello_reader.name().expect("failed to get name"); + if !_name.is_empty() { + // Valid + } + } + } + loop {} } diff --git a/personas/researcher.md b/personas/researcher.md index 4920e50..8290f5a 100644 --- a/personas/researcher.md +++ b/personas/researcher.md @@ -2,7 +2,7 @@ You are the Researcher. Your role is to conduct a comprehensive initial analysis Your workflow: 1. Analyze the problem statement to identify key technical requirements and unknown areas. -2. Use available tools (such as SearXNG and web search) to research existing libraries, frameworks, APIs, and best practices relevant to the problem. +2. Use available tools (such as SearXNG and fetch) to research existing libraries, frameworks, APIs, and best practices relevant to the problem. 3. Explore the current codebase to understand how the new functionality fits in or what existing patterns should be followed. 4. Compile a detailed report including: - Recommended tools and libraries. diff --git a/roto-tonic/Cargo.toml b/roto-tonic/Cargo.toml index 198702f..b8386cd 100644 --- a/roto-tonic/Cargo.toml +++ b/roto-tonic/Cargo.toml @@ -12,6 +12,7 @@ http-body = "1.0" http-body-util = "0.1" tower = "0.4" futures-util = "0.3" +async-trait = "0.1" tokio-stream = { version = "0.1", features = ["net"] } tokio = { version = "1.38", features = ["full"] } http = "1.1" diff --git a/roto-tonic/src/generated/interop.rs b/roto-tonic/src/generated/interop.rs index 943e8ca..b2ce666 100644 --- a/roto-tonic/src/generated/interop.rs +++ b/roto-tonic/src/generated/interop.rs @@ -5,18 +5,16 @@ use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, use std::str; use bytes::{Bytes, BytesMut, Buf, BufMut}; use tonic::{Request, Response, Status}; -use tokio_stream::Stream; -use std::pin::Pin; use std::sync::Arc; +use std::pin::Pin; use std::task::{Context, Poll}; use std::future::Future; -use tonic::body::BoxBody; use tower::Service; -use futures_util::StreamExt; -use http_body_util::BodyExt; -use http_body::Body; +use tonic::body::BoxBody; +use tokio_stream::Stream; use crate::{BufferPool, StatusBody}; - +use async_trait::async_trait; +use http_body_util::BodyExt; pub struct UnaryRequest<'a> { accessor: roto_runtime::ProtoAccessor<'a>, @@ -406,7 +404,8 @@ impl roto_runtime::RotoMessage for OwnedStreamingResponse { } } -#[tonic::async_trait] + +#[async_trait] pub trait InteropService: Send + Sync + 'static { async fn unary_call(&self, request: Request) -> std::result::Result, Status>; async fn streaming_call(&self, request: Request) -> std::result::Result> + Send>>>, Status>;