Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ std = ["alloc"]
__internal_use_only_features_that_work_on_stable = [
"alloc",
"derive",
"serde",
"simd",
"std",
]

[dependencies]
serde = { version = "1.0.144", default-features = false, optional = true }
zerocopy-derive = { version = "=0.8.31", path = "zerocopy-derive", optional = true }

# The "associated proc macro pattern" ensures that the versions of zerocopy and
Expand All @@ -132,6 +134,7 @@ rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
rustversion = "1.0"
static_assertions = "1.1"
testutil = { path = "testutil" }
serde_json = "1.0.107"
# Pinned to a specific version so that the version used for local development
# and the version used in CI are guaranteed to be the same. Future versions
# sometimes change the output format slightly, so a version mismatch can cause
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ for network parsing.
derives as `use zerocopy_derive::*` rather than by name (e.g., `use
zerocopy_derive::FromBytes`).

- **`serde`**
Provides [`serde`](https://github.com/serde-rs/serde) `Serialize` and `Deserialize` impls for
the `byteorder` numeric wrappers by delegating to their underlying primitive types.

- **`simd`**
When the `simd` feature is enabled, `FromZeros`, `FromBytes`, and
`IntoBytes` impls are emitted for all stable SIMD types which exist on the
Expand Down
131 changes: 131 additions & 0 deletions src/byteorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,36 @@ example of how it can be used for parsing UDP packets.
}
}

#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
#[cfg(feature = "serde")]
impl<O: ByteOrder> serde::Serialize for $name<O>
where
$native: serde::Serialize,
{
#[inline(always)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
$native::serialize(&self.get(), serializer)
}
}

#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
#[cfg(feature = "serde")]
impl<'de, O: ByteOrder> serde::Deserialize<'de> for $name<O>
where
$native: serde::Deserialize<'de>,
{
#[inline(always)]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
$native::deserialize(deserializer).map(Self::from)
}
}

$(
impl<O: ByteOrder> From<$name<O>> for $larger_native {
#[inline(always)]
Expand Down Expand Up @@ -1562,3 +1592,104 @@ mod tests {
assert_eq!(val_be_i16.cmp(&val_be_i16), core::cmp::Ordering::Equal);
}
}

#[cfg(all(test, feature = "serde"))]
mod serialization_tests {
use core::fmt::Debug;

use serde::{Deserialize, Serialize};

use crate::{
byteorder::{Isize, Usize, F32, F64, I128, I16, I32, I64, U128, U16, U32, U64},
BigEndian, LittleEndian,
};

fn assert_roundtrip<PrimitiveType, WrapperType>(
to_serialize: WrapperType,
primitive_value: Option<PrimitiveType>,
) where
WrapperType: Serialize + for<'de> Deserialize<'de> + PartialEq + Debug,
PrimitiveType: Serialize + for<'de> Deserialize<'de> + PartialEq + Debug,
{
let serialized_value =
serde_json::to_string(&to_serialize).expect("Serialization to json should succeed");
let deserialized_wrapper = serde_json::from_str(&serialized_value)
.expect("Deserialization from json to wrapper type should succeed");
assert_eq!(to_serialize, deserialized_wrapper);

if let Some(primitive_value) = primitive_value {
let deserialized_primitive = serde_json::from_str(&serialized_value)
.expect("Deserialization from json to primitive type should succeed");
assert_eq!(primitive_value, deserialized_primitive);
}
}

macro_rules! assert_serialization_roundtrip {
($prim:ty, $wrapper:ident, $value:expr) => {{
let primitive_value: $prim = $value;
assert_roundtrip($wrapper::<BigEndian>::new(primitive_value), Some(primitive_value));
assert_roundtrip($wrapper::<LittleEndian>::new(primitive_value), Some(primitive_value));
}};
}

#[test]
fn serialize_native_primitives() {
assert_serialization_roundtrip!(u16, U16, 0xABCDu16);
assert_serialization_roundtrip!(i16, I16, -123i16);
assert_serialization_roundtrip!(u32, U32, 0x89AB_CDEFu32);
assert_serialization_roundtrip!(i32, I32, -0x1234_5678i32);
assert_serialization_roundtrip!(u64, U64, 0x0123_4567_89AB_CDEFu64);
assert_serialization_roundtrip!(i64, I64, -0x0123_4567_89AB_CDEFi64);
assert_serialization_roundtrip!(u128, U128, 0x1234u128);
assert_serialization_roundtrip!(i128, I128, -0x1234i128);
assert_serialization_roundtrip!(usize, Usize, 0xBEEFusize);
assert_serialization_roundtrip!(isize, Isize, -12isize);
assert_serialization_roundtrip!(f32, F32, 1.25f32);
assert_serialization_roundtrip!(f64, F64, -0.75f64);
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct SerializableStruct<WrapperTypeA, WrapperTypeB>
where
WrapperTypeA: PartialEq + Debug,
WrapperTypeB: PartialEq + Debug,
{
value_a: WrapperTypeA,
value_b: [WrapperTypeB; 2],
value_c: (WrapperTypeA, WrapperTypeB),
}

macro_rules! assert_struct_serialization_roundtrip {
($prim:ty, $wrapper:ident, $value:expr) => {{
let primitive_value: $prim = $value;
let test_struct = SerializableStruct {
value_a: $wrapper::<BigEndian>::new(primitive_value),
value_b: [
$wrapper::<LittleEndian>::new(primitive_value),
$wrapper::<LittleEndian>::new(primitive_value),
],
value_c: (
$wrapper::<BigEndian>::new(primitive_value),
$wrapper::<LittleEndian>::new(primitive_value),
),
};
assert_roundtrip(test_struct, None::<$prim>);
}};
}

#[test]
fn serialize_struct() {
assert_struct_serialization_roundtrip!(u16, U16, 0xABCDu16);
assert_struct_serialization_roundtrip!(i16, I16, -123i16);
assert_struct_serialization_roundtrip!(u32, U32, 0x89AB_CDEFu32);
assert_struct_serialization_roundtrip!(i32, I32, -0x1234_5678i32);
assert_struct_serialization_roundtrip!(u64, U64, 0x0123_4567_89AB_CDEFu64);
assert_struct_serialization_roundtrip!(i64, I64, -0x0123_4567_89AB_CDEFi64);
assert_struct_serialization_roundtrip!(u128, U128, 0x1234u128);
assert_struct_serialization_roundtrip!(i128, I128, -0x1234i128);
assert_struct_serialization_roundtrip!(usize, Usize, 0xBEEFusize);
assert_struct_serialization_roundtrip!(isize, Isize, -12isize);
assert_struct_serialization_roundtrip!(f32, F32, 1.25f32);
assert_struct_serialization_roundtrip!(f64, F64, -0.75f64);
}
}
8 changes: 6 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@
//! derives as `use zerocopy_derive::*` rather than by name (e.g., `use
//! zerocopy_derive::FromBytes`).
//!
//! - **`serde`**
//! Provides [`serde`](https://github.com/serde-rs/serde) `Serialize` and `Deserialize` impls for
//! the `byteorder` numeric wrappers by delegating to their underlying primitive types.
//!
//! - **`simd`**
//! When the `simd` feature is enabled, `FromZeros`, `FromBytes`, and
//! `IntoBytes` impls are emitted for all stable SIMD types which exist on the
//! When the `simd` feature is enabled, [`FromZeros`], [`FromBytes`], and
//! [`IntoBytes`] impls are emitted for all stable SIMD types which exist on the
//! target platform. Note that the layout of SIMD types is not yet stabilized,
//! so these impls may be removed in the future if layout changes make them
//! invalid. For more information, see the Unsafe Code Guidelines Reference
Expand Down
10 changes: 5 additions & 5 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1465,10 +1465,10 @@ mod tests {
}

test!(ZstDst, 8, 0, Some(0));
test!(ZstDst, 7, 0, None);
test!(ZstDst, 7, 0, None::<usize>);

test!(ZstDst, 8, usize::MAX, Some(usize::MAX));
test!(ZstDst, 7, usize::MAX, None);
test!(ZstDst, 7, usize::MAX, None::<usize>);

#[derive(KnownLayout, Immutable)]
#[repr(C)]
Expand All @@ -1478,14 +1478,14 @@ mod tests {
}

test!(Dst, 8, 0, Some(0));
test!(Dst, 7, 0, None);
test!(Dst, 7, 0, None::<usize>);

test!(Dst, 9, 1, Some(1));
test!(Dst, 8, 1, None);
test!(Dst, 8, 1, None::<usize>);

// If we didn't properly check for overflow, this would cause the
// metadata to overflow to 0, and thus the cast would spuriously
// succeed.
test!(Dst, 8, usize::MAX - 8 + 1, None);
test!(Dst, 8, usize::MAX - 8 + 1, None::<usize>);
}
}
16 changes: 12 additions & 4 deletions tools/generate-readme/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,23 @@ fn main() {

let readme = String::from_utf8(output.stdout).unwrap();

// This regex is used to strip code links like:
// This regex is used to strip code links without url after, like:
//
// /// Here is a link to [`Vec`].
//
// These links don't work in a Markdown file, and so we remove the `[` and `]`
// characters to convert them to non-link code snippets.
let body = Regex::new(r"\[(`[^`]*`)\]")
.unwrap()
.replace_all(&readme, |caps: &Captures| caps[1].to_string());
let re = Regex::new(r"\[(`[^`]*`)\](\([^)]*\))?").unwrap();

let body = re.replace_all(&readme, |caps: &Captures| {
if caps.get(2).is_some() {
// There is a following `(...)`: keep the whole original text
caps[0].to_string()
} else {
// No `(...)`: strip the surrounding [ ]
caps[1].to_string()
}
});

println!("{}\n\n{}\n{}", COPYRIGHT_HEADER, body, DISCLAIMER_FOOTER);
}