From b20a89a5187511b27a5839a791702582e9fbe810 Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 31 Oct 2024 01:21:06 +0100 Subject: [PATCH 1/4] Barely tested --- source/postcard-schema/src/lib.rs | 1 + source/postcard-schema/src/max_size.rs | 123 +++++++++++++++++++++++ source/postcard-schema/tests/max_size.rs | 28 ++++++ 3 files changed, 152 insertions(+) create mode 100644 source/postcard-schema/src/max_size.rs create mode 100644 source/postcard-schema/tests/max_size.rs diff --git a/source/postcard-schema/src/lib.rs b/source/postcard-schema/src/lib.rs index cd7016d..08ea106 100644 --- a/source/postcard-schema/src/lib.rs +++ b/source/postcard-schema/src/lib.rs @@ -5,6 +5,7 @@ pub mod impls; pub mod schema; +pub mod max_size; #[cfg(feature = "derive")] pub use postcard_derive::Schema; diff --git a/source/postcard-schema/src/max_size.rs b/source/postcard-schema/src/max_size.rs new file mode 100644 index 0000000..e4b0755 --- /dev/null +++ b/source/postcard-schema/src/max_size.rs @@ -0,0 +1,123 @@ +//! . + +use crate::{schema::{DataModelType, DataModelVariant, NamedType}, Schema}; + +pub const fn max_size() -> Option { + max_size_nt(T::SCHEMA) +} + +pub const fn max_size_nt(nt: &NamedType) -> Option { + max_size_dmt(nt.ty) +} + +pub const fn max_size_dmt(dmt: &DataModelType) -> Option { + match dmt { + DataModelType::Bool => Some(1), + DataModelType::I8 => Some(1), + DataModelType::U8 => Some(1), + DataModelType::I16 => Some(3), + DataModelType::I32 => Some(5), + DataModelType::I64 => Some(10), + DataModelType::I128 => Some(19), + DataModelType::U16 => Some(3), + DataModelType::U32 => Some(5), + DataModelType::U64 => Some(10), + DataModelType::U128 => Some(19), + DataModelType::Usize => None, // TODO: these don't impl schema and are platform dependent + DataModelType::Isize => None, // TODO: these don't impl schema and are platform dependent + DataModelType::F32 => Some(4), + DataModelType::F64 => Some(8), + DataModelType::Char => Some(5), // I think? 1 len + up to 4 bytes + DataModelType::String => None, + DataModelType::ByteArray => None, + DataModelType::Option(nt) => max_size_nt(nt), + DataModelType::Unit => Some(0), + DataModelType::UnitStruct => Some(0), + DataModelType::NewtypeStruct(nt) => max_size_nt(nt), + DataModelType::Seq(_) => None, + DataModelType::Tuple(nts) => { + let mut i = 0; + let mut ct = 0; + while i < nts.len() { + let Some(sz) = max_size_nt(nts[i]) else { + return None; + }; + ct += sz; + i += 1; + } + Some(ct) + }, + DataModelType::TupleStruct(_) => todo!(), + DataModelType::Map { key, val } => { + let Some(sz1) = max_size_nt(key) else { + return None; + }; + let Some(sz2) = max_size_nt(val) else { + return None; + }; + Some(sz1 + sz2) + }, + DataModelType::Struct(nvals) => { + let mut i = 0; + let mut ct = 0; + while i < nvals.len() { + let Some(sz) = max_size_dmt(nvals[i].ty.ty) else { + return None; + }; + ct += sz; + i += 1; + } + Some(ct) + }, + DataModelType::Enum(nvars) => { + let mut i = 0; + let mut max = 0; + while i < nvars.len() { + let sz = match nvars[i].ty { + DataModelVariant::UnitVariant => 0, + DataModelVariant::NewtypeVariant(nt) => { + let Some(sz) = max_size_nt(nt) else { + return None; + }; + sz + }, + DataModelVariant::TupleVariant(nts) => { + let mut j = 0; + let mut ct = 0; + while j < nts.len() { + let Some(sz) = max_size_nt(nts[j]) else { + return None; + }; + ct += sz; + j += 1; + } + ct + }, + DataModelVariant::StructVariant(nvars) => { + let mut j = 0; + let mut ct = 0; + while j < nvars.len() { + let Some(sz) = max_size_dmt(nvars[j].ty.ty) else { + return None; + }; + ct += sz; + j += 1; + } + ct + }, + }; + + if sz > max { + max = sz; + } + + i += 1; + } + // discriminants are `varint(u32)` + Some(max + 5) + }, + DataModelType::Schema => None, + } +} + + diff --git a/source/postcard-schema/tests/max_size.rs b/source/postcard-schema/tests/max_size.rs new file mode 100644 index 0000000..956349c --- /dev/null +++ b/source/postcard-schema/tests/max_size.rs @@ -0,0 +1,28 @@ +#![allow(dead_code)] + +use postcard_schema::{max_size::max_size, Schema}; + +#[derive(Schema)] +enum ExampleE { + Foo, + Bar([u8; 5]), + Baz { + a: Result, + b: i128, + } +} + +#[derive(Schema)] +struct ExampleS { + a: Result, + b: i128, + c: bool, +} + +#[test] +fn smoke() { + let max1 = max_size::(); + let max2 = max_size::(); + assert_eq!(Some(34), max1); + assert_eq!(Some(30), max2); +} From 37f11fd0a351e1083133793342f431bfa2a92696 Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 31 Oct 2024 01:23:37 +0100 Subject: [PATCH 2/4] Remove todo --- source/postcard-schema/src/max_size.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/postcard-schema/src/max_size.rs b/source/postcard-schema/src/max_size.rs index e4b0755..627042d 100644 --- a/source/postcard-schema/src/max_size.rs +++ b/source/postcard-schema/src/max_size.rs @@ -35,7 +35,7 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { DataModelType::UnitStruct => Some(0), DataModelType::NewtypeStruct(nt) => max_size_nt(nt), DataModelType::Seq(_) => None, - DataModelType::Tuple(nts) => { + DataModelType::Tuple(nts) | DataModelType::TupleStruct(nts) => { let mut i = 0; let mut ct = 0; while i < nts.len() { @@ -47,7 +47,6 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { } Some(ct) }, - DataModelType::TupleStruct(_) => todo!(), DataModelType::Map { key, val } => { let Some(sz1) = max_size_nt(key) else { return None; From 7c172ccff5aeaa8fa04aa31ee8f239df1ab9f6e6 Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 31 Oct 2024 01:36:13 +0100 Subject: [PATCH 3/4] Fix a few bugs --- source/postcard-schema/src/lib.rs | 2 +- source/postcard-schema/src/max_size.rs | 40 +++++++++++++----------- source/postcard-schema/tests/max_size.rs | 9 ++---- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/source/postcard-schema/src/lib.rs b/source/postcard-schema/src/lib.rs index 08ea106..c5974f8 100644 --- a/source/postcard-schema/src/lib.rs +++ b/source/postcard-schema/src/lib.rs @@ -4,8 +4,8 @@ //! # Postcard Schema pub mod impls; -pub mod schema; pub mod max_size; +pub mod schema; #[cfg(feature = "derive")] pub use postcard_derive::Schema; diff --git a/source/postcard-schema/src/max_size.rs b/source/postcard-schema/src/max_size.rs index 627042d..4268f66 100644 --- a/source/postcard-schema/src/max_size.rs +++ b/source/postcard-schema/src/max_size.rs @@ -1,15 +1,21 @@ //! . -use crate::{schema::{DataModelType, DataModelVariant, NamedType}, Schema}; +use crate::{ + schema::{DataModelType, DataModelVariant, NamedType}, + Schema, +}; +/// Calculate the max size of a type that impls Schema pub const fn max_size() -> Option { max_size_nt(T::SCHEMA) } +/// Calculate the max size of a NamedType pub const fn max_size_nt(nt: &NamedType) -> Option { max_size_dmt(nt.ty) } +/// Calculate the max size of a DataModelType pub const fn max_size_dmt(dmt: &DataModelType) -> Option { match dmt { DataModelType::Bool => Some(1), @@ -46,16 +52,8 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { i += 1; } Some(ct) - }, - DataModelType::Map { key, val } => { - let Some(sz1) = max_size_nt(key) else { - return None; - }; - let Some(sz2) = max_size_nt(val) else { - return None; - }; - Some(sz1 + sz2) - }, + } + DataModelType::Map { .. } => None, DataModelType::Struct(nvals) => { let mut i = 0; let mut ct = 0; @@ -67,7 +65,7 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { i += 1; } Some(ct) - }, + } DataModelType::Enum(nvars) => { let mut i = 0; let mut max = 0; @@ -79,7 +77,7 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { return None; }; sz - }, + } DataModelVariant::TupleVariant(nts) => { let mut j = 0; let mut ct = 0; @@ -91,7 +89,7 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { j += 1; } ct - }, + } DataModelVariant::StructVariant(nvars) => { let mut j = 0; let mut ct = 0; @@ -103,7 +101,7 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { j += 1; } ct - }, + } }; if sz > max { @@ -112,11 +110,15 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { i += 1; } + let disc_size = if nvars.is_empty() { + 1 + } else { + // CEIL(variants / 7) + (nvars.len() + 6) / 7 + }; // discriminants are `varint(u32)` - Some(max + 5) - }, + Some(max + disc_size) + } DataModelType::Schema => None, } } - - diff --git a/source/postcard-schema/tests/max_size.rs b/source/postcard-schema/tests/max_size.rs index 956349c..1c021ce 100644 --- a/source/postcard-schema/tests/max_size.rs +++ b/source/postcard-schema/tests/max_size.rs @@ -6,10 +6,7 @@ use postcard_schema::{max_size::max_size, Schema}; enum ExampleE { Foo, Bar([u8; 5]), - Baz { - a: Result, - b: i128, - } + Baz { a: Result, b: i128 }, } #[derive(Schema)] @@ -23,6 +20,6 @@ struct ExampleS { fn smoke() { let max1 = max_size::(); let max2 = max_size::(); - assert_eq!(Some(34), max1); - assert_eq!(Some(30), max2); + assert_eq!(Some(26), max1); + assert_eq!(Some(26), max2); } From 155b157bced93c7461ab9917c6ac250bde401a59 Mon Sep 17 00:00:00 2001 From: James Munns Date: Sat, 9 Nov 2024 16:33:21 +0100 Subject: [PATCH 4/4] Ugh, nalgebra. --- source/postcard-schema/Cargo.toml | 6 + source/postcard-schema/src/const_helpers.rs | 192 ++++++++++++++++++ .../postcard-schema/src/impls/chrono_v0_4.rs | 6 + .../src/impls/heapless_v0_7.rs | 14 ++ .../src/impls/heapless_v0_8.rs | 14 ++ .../src/impls/nalgebra_v0_33.rs | 17 +- source/postcard-schema/src/lib.rs | 22 ++ source/postcard-schema/src/max_size.rs | 85 +++++++- 8 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 source/postcard-schema/src/const_helpers.rs diff --git a/source/postcard-schema/Cargo.toml b/source/postcard-schema/Cargo.toml index 4a8e7ef..e65d05d 100644 --- a/source/postcard-schema/Cargo.toml +++ b/source/postcard-schema/Cargo.toml @@ -58,6 +58,12 @@ version = "0.33.0" optional = true default-features = false +[dev-dependencies.nalgebra_v0_33] +package = "nalgebra" +version = "0.33.0" +default-features = false +features = ["serde-serialize-no-std"] + [dependencies.postcard-derive] path = "../postcard-derive" version = "0.2.0" diff --git a/source/postcard-schema/src/const_helpers.rs b/source/postcard-schema/src/const_helpers.rs new file mode 100644 index 0000000..41a0630 --- /dev/null +++ b/source/postcard-schema/src/const_helpers.rs @@ -0,0 +1,192 @@ +//! Const shims for operating on schema types + +////////////////////////////////////////////////////////////////////////////// +// STAGE 0 - HELPERS +////////////////////////////////////////////////////////////////////////////// + +use crate::schema::{DataModelType, DataModelVariant, NamedType, NamedValue, NamedVariant}; + +/// `is_prim` returns whether the type is a *primitive*, or a built-in type that +/// does not need to be sent over the wire. +pub const fn is_prim(dmt: &DataModelType) -> bool { + match dmt { + // These are all primitives + DataModelType::Bool => true, + DataModelType::I8 => true, + DataModelType::U8 => true, + DataModelType::I16 => true, + DataModelType::I32 => true, + DataModelType::I64 => true, + DataModelType::I128 => true, + DataModelType::U16 => true, + DataModelType::U32 => true, + DataModelType::U64 => true, + DataModelType::U128 => true, + DataModelType::Usize => true, + DataModelType::Isize => true, + DataModelType::F32 => true, + DataModelType::F64 => true, + DataModelType::Char => true, + DataModelType::String => true, + DataModelType::ByteArray => true, + DataModelType::Unit => true, + DataModelType::Schema => true, + + // A unit-struct is always named, so it is not primitive, as the + // name has meaning even without a value + DataModelType::UnitStruct => false, + // Items with subtypes are composite, and therefore not primitives, as + // we need to convey this information. + DataModelType::Option(_) | DataModelType::NewtypeStruct(_) | DataModelType::Seq(_) => false, + DataModelType::Tuple(_) | DataModelType::TupleStruct(_) => false, + DataModelType::Map { .. } => false, + DataModelType::Struct(_) => false, + DataModelType::Enum(_) => false, + } +} + +/// A const version of `::eq` +pub const fn str_eq(a: &str, b: &str) -> bool { + let mut i = 0; + if a.len() != b.len() { + return false; + } + let a_by = a.as_bytes(); + let b_by = b.as_bytes(); + while i < a.len() { + if a_by[i] != b_by[i] { + return false; + } + i += 1; + } + true +} + +/// A const version of `::eq` +pub const fn nty_eq(a: &NamedType, b: &NamedType) -> bool { + str_eq(a.name, b.name) && dmt_eq(a.ty, b.ty) +} + +/// A const version of `<[&NamedType] as PartialEq>::eq` +pub const fn ntys_eq(a: &[&NamedType], b: &[&NamedType]) -> bool { + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if !nty_eq(a[i], b[i]) { + return false; + } + i += 1; + } + true +} + +/// A const version of `::eq` +pub const fn dmt_eq(a: &DataModelType, b: &DataModelType) -> bool { + match (a, b) { + // Data model types are ONLY matching if they are both the same variant + // + // For primitives (and unit structs), we only check the discriminant matches. + (DataModelType::Bool, DataModelType::Bool) => true, + (DataModelType::I8, DataModelType::I8) => true, + (DataModelType::U8, DataModelType::U8) => true, + (DataModelType::I16, DataModelType::I16) => true, + (DataModelType::I32, DataModelType::I32) => true, + (DataModelType::I64, DataModelType::I64) => true, + (DataModelType::I128, DataModelType::I128) => true, + (DataModelType::U16, DataModelType::U16) => true, + (DataModelType::U32, DataModelType::U32) => true, + (DataModelType::U64, DataModelType::U64) => true, + (DataModelType::U128, DataModelType::U128) => true, + (DataModelType::Usize, DataModelType::Usize) => true, + (DataModelType::Isize, DataModelType::Isize) => true, + (DataModelType::F32, DataModelType::F32) => true, + (DataModelType::F64, DataModelType::F64) => true, + (DataModelType::Char, DataModelType::Char) => true, + (DataModelType::String, DataModelType::String) => true, + (DataModelType::ByteArray, DataModelType::ByteArray) => true, + (DataModelType::Unit, DataModelType::Unit) => true, + (DataModelType::UnitStruct, DataModelType::UnitStruct) => true, + (DataModelType::Schema, DataModelType::Schema) => true, + + // For non-primitive types, we check whether all children are equivalent as well. + (DataModelType::Option(nta), DataModelType::Option(ntb)) => nty_eq(nta, ntb), + (DataModelType::NewtypeStruct(nta), DataModelType::NewtypeStruct(ntb)) => nty_eq(nta, ntb), + (DataModelType::Seq(nta), DataModelType::Seq(ntb)) => nty_eq(nta, ntb), + + (DataModelType::Tuple(ntsa), DataModelType::Tuple(ntsb)) => ntys_eq(ntsa, ntsb), + (DataModelType::TupleStruct(ntsa), DataModelType::TupleStruct(ntsb)) => ntys_eq(ntsa, ntsb), + ( + DataModelType::Map { + key: keya, + val: vala, + }, + DataModelType::Map { + key: keyb, + val: valb, + }, + ) => nty_eq(keya, keyb) && nty_eq(vala, valb), + (DataModelType::Struct(nvalsa), DataModelType::Struct(nvalsb)) => vals_eq(nvalsa, nvalsb), + (DataModelType::Enum(nvarsa), DataModelType::Enum(nvarsb)) => vars_eq(nvarsa, nvarsb), + + // Any mismatches are not equal + _ => false, + } +} + +/// A const version of `::eq` +pub const fn var_eq(a: &NamedVariant, b: &NamedVariant) -> bool { + str_eq(a.name, b.name) && dmv_eq(a.ty, b.ty) +} + +/// A const version of `<&[&NamedVariant] as PartialEq>::eq` +pub const fn vars_eq(a: &[&NamedVariant], b: &[&NamedVariant]) -> bool { + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if !var_eq(a[i], b[i]) { + return false; + } + i += 1; + } + true +} + +/// A const version of `<&[&NamedValue] as PartialEq>::eq` +pub const fn vals_eq(a: &[&NamedValue], b: &[&NamedValue]) -> bool { + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if !str_eq(a[i].name, b[i].name) { + return false; + } + if !nty_eq(a[i].ty, b[i].ty) { + return false; + } + + i += 1; + } + true +} + +/// A const version of `::eq` +pub const fn dmv_eq(a: &DataModelVariant, b: &DataModelVariant) -> bool { + match (a, b) { + (DataModelVariant::UnitVariant, DataModelVariant::UnitVariant) => true, + (DataModelVariant::NewtypeVariant(nta), DataModelVariant::NewtypeVariant(ntb)) => { + nty_eq(nta, ntb) + } + (DataModelVariant::TupleVariant(ntsa), DataModelVariant::TupleVariant(ntsb)) => { + ntys_eq(ntsa, ntsb) + } + (DataModelVariant::StructVariant(nvarsa), DataModelVariant::StructVariant(nvarsb)) => { + vals_eq(nvarsa, nvarsb) + } + _ => false, + } +} diff --git a/source/postcard-schema/src/impls/chrono_v0_4.rs b/source/postcard-schema/src/impls/chrono_v0_4.rs index 8c439e1..4697054 100644 --- a/source/postcard-schema/src/impls/chrono_v0_4.rs +++ b/source/postcard-schema/src/impls/chrono_v0_4.rs @@ -8,4 +8,10 @@ impl Schema for chrono_v0_4::DateTime { name: "DateTime", ty: <&str>::SCHEMA.ty, }; + // TODO: Can we implement manual maxsize for this? The default serialization + // repr is RFC3339, which I think is bounded, but users can opt into alternative + // reprs using `serde_with`, see https://docs.rs/chrono/latest/chrono/serde/index.html + // for more details. + // + // A PR is welcome here, if someone needs to calculate max size for chrono types. } diff --git a/source/postcard-schema/src/impls/heapless_v0_7.rs b/source/postcard-schema/src/impls/heapless_v0_7.rs index e215189..a79f311 100644 --- a/source/postcard-schema/src/impls/heapless_v0_7.rs +++ b/source/postcard-schema/src/impls/heapless_v0_7.rs @@ -1,5 +1,6 @@ //! Implementations of the [`Schema`] trait for the `heapless` crate v0.7 +use crate::max_size::{bounded_seq_max, bounded_string_max}; use crate::{ schema::{DataModelType, NamedType}, Schema, @@ -11,6 +12,7 @@ impl Schema for heapless_v0_7::Vec { name: "heapless::Vec", ty: &DataModelType::Seq(T::SCHEMA), }; + const MANUAL_MAX_SIZE: Option = bounded_seq_max::(); } #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_7")))] impl Schema for heapless_v0_7::String { @@ -18,4 +20,16 @@ impl Schema for heapless_v0_7::String { name: "heapless::String", ty: &DataModelType::String, }; + const MANUAL_MAX_SIZE: Option = bounded_string_max::(); +} + +#[cfg(test)] +mod test { + use crate::max_size::max_size; + + #[test] + fn smoke() { + assert_eq!(max_size::>(), Some(130)); + assert_eq!(max_size::>(), Some(130)); + } } diff --git a/source/postcard-schema/src/impls/heapless_v0_8.rs b/source/postcard-schema/src/impls/heapless_v0_8.rs index 09f9cfa..2200cff 100644 --- a/source/postcard-schema/src/impls/heapless_v0_8.rs +++ b/source/postcard-schema/src/impls/heapless_v0_8.rs @@ -1,5 +1,6 @@ //! Implementations of the [`Schema`] trait for the `heapless` crate v0.8 +use crate::max_size::{bounded_seq_max, bounded_string_max}; use crate::{ schema::{DataModelType, NamedType}, Schema, @@ -11,6 +12,7 @@ impl Schema for heapless_v0_8::Vec { name: "heapless::Vec", ty: &DataModelType::Seq(T::SCHEMA), }; + const MANUAL_MAX_SIZE: Option = bounded_seq_max::(); } #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_8")))] impl Schema for heapless_v0_8::String { @@ -18,4 +20,16 @@ impl Schema for heapless_v0_8::String { name: "heapless::String", ty: &DataModelType::String, }; + const MANUAL_MAX_SIZE: Option = bounded_string_max::(); +} + +#[cfg(test)] +mod test { + use crate::max_size::max_size; + + #[test] + fn smoke() { + assert_eq!(max_size::>(), Some(130)); + assert_eq!(max_size::>(), Some(130)); + } } diff --git a/source/postcard-schema/src/impls/nalgebra_v0_33.rs b/source/postcard-schema/src/impls/nalgebra_v0_33.rs index 61de71e..edb1889 100644 --- a/source/postcard-schema/src/impls/nalgebra_v0_33.rs +++ b/source/postcard-schema/src/impls/nalgebra_v0_33.rs @@ -1,7 +1,7 @@ //! Implementations of the [`Schema`] trait for the `nalgebra` crate v0.33 use crate::{ - schema::{DataModelType, NamedType}, + schema::NamedType, Schema, }; @@ -16,8 +16,21 @@ impl Schema where T: Schema + nalgebra_v0_33::Scalar, { + /// Warning! const SCHEMA: &'static NamedType = &NamedType { name: "nalgebra::Matrix>", - ty: &DataModelType::Seq(T::SCHEMA), + // NOTE: This is not TECHNICALLY correct. + ty: <[[T; R]; C]>::SCHEMA.ty, }; } + +#[cfg(test)] +mod test { + use nalgebra_v0_33::Const; + #[test] + fn smoke() { + let x = nalgebra_v0_33::Matrix::, Const<3>, _>::new(1, 2, 3, 4, 5, 6, 7, 8, 9); + let y = postcard::to_stdvec(&x).unwrap(); + assert_eq!(&[1, 4, 7, 2, 5, 8, 3, 6, 9], y.as_slice()); + } +} diff --git a/source/postcard-schema/src/lib.rs b/source/postcard-schema/src/lib.rs index c5974f8..8693014 100644 --- a/source/postcard-schema/src/lib.rs +++ b/source/postcard-schema/src/lib.rs @@ -6,6 +6,7 @@ pub mod impls; pub mod max_size; pub mod schema; +pub mod const_helpers; #[cfg(feature = "derive")] pub use postcard_derive::Schema; @@ -15,4 +16,25 @@ pub trait Schema { /// A recursive data structure that describes the schema of the given /// type. const SCHEMA: &'static schema::NamedType; + + /// A manually calculated maximum size hint. + /// + /// This is useful in the case that a foreign type is expressed + /// in some form that has a technically unbounded max size, for + /// example a byte slice, but in reality it never sends more than + /// some calcuable size. + /// + /// A good example is `heapless::Vec`, which is expressed + /// on the wire as a `Seq(N)`, which is unbounded, however the + /// true max size is the length to store N as a varint plus + /// `N * max_size::`, which is calcuable. + /// + /// This value should not typically be used directly, instead use + /// [`max_size()`][crate::max_size::max_size()], which will EITHER + /// use the manual max size, or calculate the max size from the + /// schema. + /// + /// You must not rely on this value for safety reasons, as implementations + /// could be wrong. + const MANUAL_MAX_SIZE: Option = None; } diff --git a/source/postcard-schema/src/max_size.rs b/source/postcard-schema/src/max_size.rs index 4268f66..aba94fe 100644 --- a/source/postcard-schema/src/max_size.rs +++ b/source/postcard-schema/src/max_size.rs @@ -1,13 +1,20 @@ //! . use crate::{ - schema::{DataModelType, DataModelVariant, NamedType}, - Schema, + const_helpers::nty_eq, schema::{DataModelType, DataModelVariant, NamedType}, Schema }; /// Calculate the max size of a type that impls Schema +/// +/// This number must NOT be relied on for safety purposes +/// (such as unchecked access), as manual schema impls can +/// be wrong. pub const fn max_size() -> Option { - max_size_nt(T::SCHEMA) + if let Some(sz) = T::MANUAL_MAX_SIZE { + Some(sz) + } else { + max_size_nt(T::SCHEMA) + } } /// Calculate the max size of a NamedType @@ -15,6 +22,42 @@ pub const fn max_size_nt(nt: &NamedType) -> Option { max_size_dmt(nt.ty) } +/// . +pub const fn bounded_seq_max() -> Option { + let DataModelType::Seq(nt) = Outer::SCHEMA.ty else { + panic!("Not a bounded seq!"); + }; + assert!(nty_eq(nt, Inner::SCHEMA), "Mismatched Outer/Inner types!"); + let size_one = max_size::(); + if let Some(sz) = size_one { + let data_sz = sz * N; + let varint_sz = size_as_varint_usize(N); + Some(data_sz + varint_sz) + } else { + None + } +} + +/// . +pub const fn bounded_string_max() -> Option { + // Measured in bytes + let data_sz = N; + let varint_sz = size_as_varint_usize(N); + Some(data_sz + varint_sz) +} + +/// Calculate the size (in bytes) it would take to store this +/// usize as a varint. +pub const fn size_as_varint_usize(n: usize) -> usize { + if n == 0 { + return 1; + } + let ttl_bits = usize::BITS as usize; + let ldg_bits = n.leading_zeros() as usize; + let used_bits = ttl_bits - ldg_bits; + (used_bits + 6) / 7 +} + /// Calculate the max size of a DataModelType pub const fn max_size_dmt(dmt: &DataModelType) -> Option { match dmt { @@ -113,8 +156,10 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { let disc_size = if nvars.is_empty() { 1 } else { - // CEIL(variants / 7) - (nvars.len() + 6) / 7 + // We need the size of the largest variant ID. This is the (len - 1), + // because if we have one variant, its discriminant will be zero. + // We already checked above that len != 0. + size_as_varint_usize(nvars.len() - 1) }; // discriminants are `varint(u32)` Some(max + disc_size) @@ -122,3 +167,33 @@ pub const fn max_size_dmt(dmt: &DataModelType) -> Option { DataModelType::Schema => None, } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn savu() { + assert_eq!(1, size_as_varint_usize(0x00)); + assert_eq!(1, size_as_varint_usize(0x7F)); + assert_eq!(2, size_as_varint_usize(0x80)); + assert_eq!(2, size_as_varint_usize(0x3FFF)); + assert_eq!(3, size_as_varint_usize(0x4000)); + assert_eq!(3, size_as_varint_usize(0xFFFF)); + + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + let _: () = { + assert_eq!(3, size_as_varint_usize(0x1FFFFF)); + assert_eq!(4, size_as_varint_usize(0x200000)); + assert_eq!(4, size_as_varint_usize(0xFFFFFFF)); + assert_eq!(5, size_as_varint_usize(0x10000000)); + assert_eq!(5, size_as_varint_usize(0xFFFFFFFF)); + }; + + #[cfg(target_pointer_width = "64")] + let _: () = { + assert_eq!(5, size_as_varint_usize(0x7FFFFFFFF)); + assert_eq!(6, size_as_varint_usize(0x800000000)); + assert_eq!(10, size_as_varint_usize(usize::MAX)); + }; + } +}