epserde/ser/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2023 Inria
3 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6 */
7
8//! Serialization traits and types.
9//!
10//! [`Serialize`] is the main serialization trait, providing a
11//! [`Serialize::serialize`] method that serializes the type into a generic
12//! [`WriteNoStd`] backend, and a [`Serialize::serialize_with_schema`] method
13//! that additionally returns a [`Schema`] describing the data that has been
14//! written. The implementation of this trait is based on [`SerInner`],
15//! which is automatically derived with `#[derive(Serialize)]`.
16
17use crate::traits::*;
18use crate::*;
19
20use core::hash::Hasher;
21
22pub mod write_with_names;
23pub use write_with_names::*;
24pub mod helpers;
25pub use helpers::*;
26pub mod write;
27pub use write::*;
28
29#[cfg(feature = "std")]
30use std::{io::BufWriter, path::Path};
31
32pub type Result<T> = core::result::Result<T, Error>;
33
34/// A shorthand for the [serialization type associated with a serializable
35/// type](SerInner::SerType).
36pub type SerType<T> = <T as SerInner>::SerType;
37
38/// Main serialization trait. It is separated from [`SerInner`] to avoid
39/// that the user modify its behavior, and hide internal serialization methods.
40///
41/// It provides a convenience method [`Serialize::store`] that serializes the
42/// type to a file.
43///
44/// # Safety
45///
46/// All serialization methods are unsafe as they write padding bytes.
47/// Serializing to such a vector and accessing such bytes will lead to undefined
48/// behavior as padding bytes are uninitialized.
49///
50/// For example, this code reads a portion of the stack:
51///
52/// ```ignore
53/// use epserde::{ser::Serialize, Epserde};
54///
55/// #[repr(C)]
56/// #[repr(align(1024))]
57/// #[epserde_zero_copy]
58///
59/// struct Example(u8);
60///
61/// let value = [Example(0), Example(1)];
62///
63/// let mut bytes = vec![];
64/// unsafe { value.serialize(&mut bytes).unwrap(); }
65///
66/// for chunk in bytes.chunks(8) {
67///     println!("{:016x}", u64::from_ne_bytes(chunk.try_into().unwrap()));
68/// }
69/// ```
70///
71/// If you are concerned about this issue, you must organize your structures so
72/// that they do not contain any padding (e.g., by creating explicit padding
73/// bytes).
74pub trait Serialize {
75    /// Serializes the type using the given backend.
76    ///
77    /// # Safety
78    ///
79    /// See the [trait documentation](Serialize).
80    unsafe fn serialize(&self, backend: &mut impl WriteNoStd) -> Result<usize> {
81        let mut write_with_pos = WriterWithPos::new(backend);
82        unsafe { self.ser_on_field_write(&mut write_with_pos) }?;
83        Ok(write_with_pos.pos())
84    }
85
86    /// Serializes the type using the given backend and return a [schema](Schema)
87    /// describing the data that has been written.
88    ///
89    /// This method is mainly useful for debugging and to check cross-language
90    /// interoperability.
91    ///
92    /// # Safety
93    ///
94    /// See the [trait documentation](Serialize).
95    unsafe fn serialize_with_schema(&self, backend: &mut impl WriteNoStd) -> Result<Schema> {
96        let mut writer_with_pos = WriterWithPos::new(backend);
97        let mut schema_writer = SchemaWriter::new(&mut writer_with_pos);
98        unsafe { self.ser_on_field_write(&mut schema_writer) }?;
99        Ok(schema_writer.schema)
100    }
101
102    /// Serializes the type using the given [`WriteWithNames`].
103    ///
104    /// # Safety
105    ///
106    /// See the [trait documentation](Serialize).
107    unsafe fn ser_on_field_write(&self, backend: &mut impl WriteWithNames) -> Result<()>;
108
109    /// Convenience method to serialize to a file.
110    ///
111    /// # Safety
112    ///
113    /// See the [trait documentation](Serialize).
114    #[cfg(feature = "std")]
115    unsafe fn store(&self, path: impl AsRef<Path>) -> Result<()> {
116        let file = std::fs::File::create(path).map_err(Error::FileOpenError)?;
117        let mut buf_writer = BufWriter::new(file);
118        unsafe { self.serialize(&mut buf_writer)? };
119        Ok(())
120    }
121}
122
123/// Inner trait to implement serialization of a type. This trait exists
124/// to separate the user-facing [`Serialize`] trait from the low-level
125/// serialization mechanism of [`SerInner::_ser_inner`]. Moreover,
126/// it makes it possible to behave slightly differently at the top
127/// of the recursion tree (e.g., to write the endianness marker).
128///
129/// The user should not implement this trait directly, but rather derive it.
130pub trait SerInner {
131    /// This is the type that will be written in the header of the file, and
132    /// thus the type that will be deserialized. In most cases it is `Self`, but
133    /// in some cases, as for [references to slices](crate::impls::slice),
134    /// it is customized.
135    type SerType;
136    /// Inner constant used by the derive macros to keep
137    /// track recursively of whether the type
138    /// satisfies the conditions for being zero-copy. It is checked
139    /// at runtime against the trait implemented by the type, and
140    /// if a [`ZeroCopy`] type has this constant set to `false`
141    /// serialization will panic.
142    const IS_ZERO_COPY: bool;
143
144    /// Inner constant used by the derive macros to keep
145    /// track of whether all fields of a type are zero-copy
146    /// but neither the attribute `#[epserde_zero_copy]` nor the attribute
147    /// `#[epserde_deep_copy]` was specified. It is checked at runtime, and if it is
148    /// true a run-time warning will be issued each time you serialize an
149    /// instance type, as the type could be zero-copy, which would be more
150    /// efficient.
151    const ZERO_COPY_MISMATCH: bool;
152
153    /// Serializes this structure using the given backend.
154    ///
155    /// # Safety
156    ///
157    /// See the documentation of [`Serialize`].
158    unsafe fn _ser_inner(&self, backend: &mut impl WriteWithNames) -> Result<()>;
159}
160
161/// Blanket implementation that prevents the user from overwriting the
162/// methods in [`Serialize`].
163///
164/// This implementation [writes a header](`write_header`) containing a magic
165/// cookie, some hashes and debug information and then delegates to
166/// [WriteWithNames::write].
167///
168/// # Implementation Notes
169///
170/// Note the bound on the serialization type or `T`: we need to be able to
171/// compute type and alignment hashes for it. We could bound the serialization
172/// type itself in the definition of [`SerInner`], but having the bound here
173/// instead gives us more flexibility and makes the implementation of
174/// [`Owned`](crate::deser::Owned) easier.
175impl<T: SerInner<SerType: TypeHash + AlignHash>> Serialize for T {
176    unsafe fn ser_on_field_write(&self, backend: &mut impl WriteWithNames) -> Result<()> {
177        // write the header using the serialization type, not the type itself
178        // this is done so that you can serialize types with reference to slices
179        // that can then be deserialized as vectors.
180        write_header::<SerType<Self>>(backend)?;
181        backend.write("ROOT", self)?;
182        backend.flush()
183    }
184}
185
186/// Writes the header.
187///
188/// Note that `S` is the serializable type, not the serialization type.
189///
190/// Must be kept in sync with [`crate::deser::check_header`].
191pub fn write_header<S: TypeHash + AlignHash>(backend: &mut impl WriteWithNames) -> Result<()> {
192    backend.write("MAGIC", &MAGIC)?;
193    backend.write("VERSION_MAJOR", &VERSION.0)?;
194    backend.write("VERSION_MINOR", &VERSION.1)?;
195    backend.write("USIZE_SIZE", &(core::mem::size_of::<usize>() as u8))?;
196
197    let mut type_hasher = xxhash_rust::xxh3::Xxh3::new();
198    S::type_hash(&mut type_hasher);
199
200    let mut align_hasher = xxhash_rust::xxh3::Xxh3::new();
201    let mut offset_of = 0;
202    S::align_hash(&mut align_hasher, &mut offset_of);
203
204    backend.write("TYPE_HASH", &type_hasher.finish())?;
205    backend.write("REPR_HASH", &align_hasher.finish())?;
206    backend.write("TYPE_NAME", &core::any::type_name::<S>())
207}
208
209/// A helper trait that makes it possible to implement differently serialization
210/// for [`crate::traits::ZeroCopy`] and [`crate::traits::DeepCopy`] types. See
211/// [`crate::traits::CopyType`] for more information.
212pub trait SerHelper<T: CopySelector> {
213    /// # Safety
214    ///
215    /// See the documentation of [`Serialize`].
216    unsafe fn _ser_inner(&self, backend: &mut impl WriteWithNames) -> Result<()>;
217}
218
219#[derive(Debug)]
220/// Errors that can happen during serialization.
221pub enum Error {
222    /// The underlying writer returned an error.
223    WriteError,
224    /// [`Serialize::store`] could not open the provided file.
225    #[cfg(feature = "std")]
226    FileOpenError(std::io::Error),
227    /// The declared length of an iterator did not match
228    /// the actual length.
229    IteratorLengthMismatch { actual: usize, expected: usize },
230}
231
232impl core::error::Error for Error {}
233
234impl core::fmt::Display for Error {
235    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
236        match self {
237            Self::WriteError => write!(f, "Write error during ε-serde serialization"),
238            #[cfg(feature = "std")]
239            Self::FileOpenError(error) => {
240                write!(
241                    f,
242                    "Error opening file during ε-serde serialization: {}",
243                    error
244                )
245            }
246            Self::IteratorLengthMismatch { actual, expected } => write!(
247                f,
248                "Iterator length mismatch during ε-serde serialization: expected {} items, got {}",
249                expected, actual
250            ),
251        }
252    }
253}