Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ cap_0084_muxed_contract = []

# Features available without any new dependencies.
type_enum = []
const = []

# Features dependent on optional dependencies.
base64 = ["std", "dep:base64"]
Expand Down
286 changes: 284 additions & 2 deletions src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,11 +774,293 @@ pub trait WriteXdr {

/// `Pad_len` returns the number of bytes to pad an XDR value of the given
/// length to make the final serialized size a multiple of 4.
#[cfg(feature = "std")]
fn pad_len(len: usize) -> usize {
#[cfg(any(feature = "std", feature = "const"))]
const fn pad_len(len: usize) -> usize {
(4 - (len % 4)) % 4
}

/// `padding` returns the zero bytes that pad an XDR value of the given length
/// out to a multiple of 4. The padding is never more than 3 bytes, so it is a
/// prefix of a single static.
#[cfg(feature = "const")]
const fn padding(len: usize) -> &'static [u8] {
const PADDING: [u8; 3] = [0; 3];
PADDING.split_at(pad_len(len)).0
}

/// `ConstWriter` serializes XDR into a fixed byte buffer using only const
/// operations.
///
/// It is the const-evaluable counterpart to [`WriteXdr::write_xdr`], producing
/// the same bytes. Unlike the streaming path it enforces no depth or length
/// limits: a const value is fixed at compile time, so there is no untrusted
/// input to bound.
///
/// The writers below are the primitives. Every type defined in the XDR files
/// additionally has a generated `write_type_{type}` method on `ConstWriter`
/// that serializes one value of that type, taking the type's borrowing `Ref`
/// form where it owns heap data and the type itself otherwise; a type that
/// appears wrapped gets `write_type_option_{type}` and `write_type_vec_{type}`
/// alongside. The `type_` distinguishes them from these primitives, so a
/// wrapper over a primitive is named `write_option_u32` rather than
/// `write_type_option_u32`.
///
/// Keeping the encoders on the writer, rather than as inherent methods on each
/// generated type, leaves each type with only a thin `const_xdr_len` and
/// `const_to_xdr` pair that wraps its writer method. Each generated method is
/// emitted into the file of the type it serializes, so the two stay together.
///
/// Serialization is infallible. The only way it can fail is a value whose
/// length does not fit the `u32` XDR length prefix, which panics; in a const
/// context that is a compile-time error.
///
/// Bytes are only stored while the running length is within the buffer; bytes
/// past the end of the buffer are counted but not written. This allows the
/// exact encoded length to be measured by serializing into an empty buffer and
/// reading [`ConstWriter::len`], then serializing again into a buffer of that
/// size.
#[cfg(feature = "const")]
pub struct ConstWriter<'a> {
buf: &'a mut [u8],
len: usize,
}

#[cfg(feature = "const")]
impl<'a> ConstWriter<'a> {
/// Constructs a new `ConstWriter` that serializes into `buf`.
#[must_use]
pub const fn new(buf: &'a mut [u8]) -> Self {
ConstWriter { buf, len: 0 }
}

/// Returns the number of bytes serialized so far, which equals the total
/// encoded length once serialization completes (even if it exceeded the
/// buffer).
#[must_use]
pub const fn len(&self) -> usize {
self.len
}

/// Returns true while nothing has been serialized yet.
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}

/// Writes `data` into the buffer, advancing the length. Bytes beyond the
/// end of the buffer are counted but not stored.
const fn write_bytes(&mut self, data: &[u8]) {
let mut i = 0;
while i < data.len() {
if self.len < self.buf.len() {
self.buf[self.len] = data[i];
}
self.len += 1;
i += 1;
}
}

/// Serializes an `i32`, mirroring `<i32 as WriteXdr>::write_xdr`.
pub const fn write_i32(&mut self, v: i32) {
self.write_bytes(&v.to_be_bytes());
}

/// Serializes a `u32`, mirroring `<u32 as WriteXdr>::write_xdr`.
pub const fn write_u32(&mut self, v: u32) {
self.write_bytes(&v.to_be_bytes());
}

/// Serializes an `i64`, mirroring `<i64 as WriteXdr>::write_xdr`.
pub const fn write_i64(&mut self, v: i64) {
self.write_bytes(&v.to_be_bytes());
}

/// Serializes a `u64`, mirroring `<u64 as WriteXdr>::write_xdr`.
pub const fn write_u64(&mut self, v: u64) {
self.write_bytes(&v.to_be_bytes());
}

/// Serializes a `bool`, mirroring `<bool as WriteXdr>::write_xdr`.
pub const fn write_bool(&mut self, v: bool) {
let i = if v { 1u32 } else { 0u32 };
self.write_u32(i);
}

/// Serializes a fixed-length opaque array with trailing padding, mirroring
/// `<[u8; N] as WriteXdr>::write_xdr`.
pub const fn write_fixed_opaque(&mut self, data: &[u8]) {
self.write_bytes(data);
self.write_bytes(padding(data.len()));
}

/// Serializes a `u32` length prefix from a `usize`, mirroring the
/// `len.try_into()` and `len.write_xdr(w)` of the variable-length
/// `WriteXdr` implementations.
///
/// ### Panics
///
/// If `len` does not fit in a `u32`. In a const context that is a
/// compile-time error.
#[allow(clippy::cast_possible_truncation)]
pub const fn write_len(&mut self, len: usize) {
assert!(len <= u32::MAX as usize, "xdr value max length exceeded");
self.write_u32(len as u32);
}

/// Serializes a variable-length opaque byte sequence: a `u32` length
/// prefix, the bytes, then trailing padding. Mirrors `<VecM<u8> as
/// WriteXdr>::write_xdr`, `<BytesM as WriteXdr>::write_xdr`, and `<StringM
/// as WriteXdr>::write_xdr`, which XDR encodes identically.
pub const fn write_var_opaque(&mut self, data: &[u8]) {
let n = data.len();
self.write_len(n);
self.write_bytes(data);
self.write_bytes(padding(n));
}

// The `Option` and `VecM` serializers below are the ones whose inner type
// is a builtin scalar. They are written by hand, beside the scalar
// serializers they call, because they have no generated type to sit with:
// the generator emits a wrapper into the file of the type it wraps, and a
// scalar has no file. Wrappers over `opaque`, `string` and defined types
// are still generated.

/// Serializes an optional `i32`, mirroring `<Option<i32> as
/// WriteXdr>::write_xdr`.
pub const fn write_option_i32(&mut self, v: &Option<i32>) {
match v {
Some(v) => {
self.write_u32(1);
self.write_i32(*v);
}
None => {
self.write_u32(0);
}
}
}

/// Serializes an optional `u32`, mirroring `<Option<u32> as
/// WriteXdr>::write_xdr`.
pub const fn write_option_u32(&mut self, v: &Option<u32>) {
match v {
Some(v) => {
self.write_u32(1);
self.write_u32(*v);
}
None => {
self.write_u32(0);
}
}
}

/// Serializes an optional `i64`, mirroring `<Option<i64> as
/// WriteXdr>::write_xdr`.
pub const fn write_option_i64(&mut self, v: &Option<i64>) {
match v {
Some(v) => {
self.write_u32(1);
self.write_i64(*v);
}
None => {
self.write_u32(0);
}
}
}

/// Serializes an optional `u64`, mirroring `<Option<u64> as
/// WriteXdr>::write_xdr`.
pub const fn write_option_u64(&mut self, v: &Option<u64>) {
match v {
Some(v) => {
self.write_u32(1);
self.write_u64(*v);
}
None => {
self.write_u32(0);
}
}
}

/// Serializes an optional `bool`, mirroring `<Option<bool> as
/// WriteXdr>::write_xdr`.
pub const fn write_option_bool(&mut self, v: &Option<bool>) {
match v {
Some(v) => {
self.write_u32(1);
self.write_bool(*v);
}
None => {
self.write_u32(0);
}
}
}

/// Serializes a variable-length array of `i32`, mirroring `<VecM<i32, MAX>
/// as WriteXdr>::write_xdr`.
pub const fn write_vec_i32<const MAX: u32>(&mut self, v: &VecMRef<'_, i32, MAX>) {
let s = v.as_slice();
let len = s.len();
self.write_len(len);
let mut i = 0usize;
while i < len {
self.write_i32(s[i]);
i += 1;
}
}

/// Serializes a variable-length array of `u32`, mirroring `<VecM<u32, MAX>
/// as WriteXdr>::write_xdr`.
pub const fn write_vec_u32<const MAX: u32>(&mut self, v: &VecMRef<'_, u32, MAX>) {
let s = v.as_slice();
let len = s.len();
self.write_len(len);
let mut i = 0usize;
while i < len {
self.write_u32(s[i]);
i += 1;
}
}

/// Serializes a variable-length array of `i64`, mirroring `<VecM<i64, MAX>
/// as WriteXdr>::write_xdr`.
pub const fn write_vec_i64<const MAX: u32>(&mut self, v: &VecMRef<'_, i64, MAX>) {
let s = v.as_slice();
let len = s.len();
self.write_len(len);
let mut i = 0usize;
while i < len {
self.write_i64(s[i]);
i += 1;
}
}

/// Serializes a variable-length array of `u64`, mirroring `<VecM<u64, MAX>
/// as WriteXdr>::write_xdr`.
pub const fn write_vec_u64<const MAX: u32>(&mut self, v: &VecMRef<'_, u64, MAX>) {
let s = v.as_slice();
let len = s.len();
self.write_len(len);
let mut i = 0usize;
while i < len {
self.write_u64(s[i]);
i += 1;
}
}

/// Serializes a variable-length array of `bool`, mirroring `<VecM<bool, MAX>
/// as WriteXdr>::write_xdr`.
pub const fn write_vec_bool<const MAX: u32>(&mut self, v: &VecMRef<'_, bool, MAX>) {
let s = v.as_slice();
let len = s.len();
self.write_len(len);
let mut i = 0usize;
while i < len {
self.write_bool(s[i]);
i += 1;
}
}
}

impl ReadXdr for i32 {
#[cfg(feature = "std")]
fn read_xdr<R: Read>(r: &mut Limited<R>) -> Result<Self, Error> {
Expand Down
54 changes: 54 additions & 0 deletions src/generated/account_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,57 @@ impl WriteXdr for AccountEntryRef<'_> {
})
}
}

#[cfg(feature = "const")]
impl AccountEntryRef<'_> {
/// The exact XDR-encoded length of this value, in bytes.
///
/// Evaluable in a const context, so a caller (such as a proc-macro) can
/// size a buffer for [`Self::const_to_xdr`] at compile time.
#[must_use]
pub const fn const_xdr_len(&self) -> usize {
let mut empty: [u8; 0] = [];
let mut w = ConstWriter::new(&mut empty);
w.write_type_account_entry(self);
w.len()
}

/// Serialize this value as XDR into a fixed-size `[u8; N]` using only const
/// operations. This is the const counterpart to [`WriteXdr::to_xdr`].
///
/// `N` must equal [`Self::const_xdr_len`]. It is intended for callers, such
/// as a proc-macro, that compute the length with `const_xdr_len` and pass
/// it as `N`; `const_to_xdr` itself does not need to call `const_xdr_len`.
///
/// # Panics
///
/// Panics if `N` does not equal the value's [`Self::const_xdr_len`].
#[must_use]
pub const fn const_to_xdr<const N: usize>(&self) -> [u8; N] {
let mut buf = [0u8; N];
let mut w = ConstWriter::new(&mut buf);
w.write_type_account_entry(self);
assert!(
w.len() == N,
"const_to_xdr: N does not equal the XDR-encoded length"
);
buf
}
}

#[cfg(feature = "const")]
impl ConstWriter<'_> {
/// Serializes a [`AccountEntry`], mirroring `<AccountEntry as WriteXdr>::write_xdr`.
pub const fn write_type_account_entry(&mut self, v: &AccountEntryRef<'_>) {
self.write_type_account_id(&v.account_id);
self.write_i64(v.balance);
self.write_type_sequence_number(&v.seq_num);
self.write_u32(v.num_sub_entries);
self.write_type_option_account_id(&v.inflation_dest);
self.write_u32(v.flags);
self.write_type_string32(&v.home_domain);
self.write_type_thresholds(&v.thresholds);
self.write_type_vec_signer(&v.signers);
self.write_type_account_entry_ext(&v.ext);
}
}
Loading
Loading