From c971f1b266f627a2f6a1f9f995b1e0356a29ca6c Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 29 Apr 2026 13:58:06 +0200 Subject: [PATCH 1/3] revert serde_bytes for VLBytes and add VLByteVec --- Cargo.lock | 1 + tls_codec/Cargo.toml | 1 + tls_codec/benches/quic_vec.rs | 3 + tls_codec/derive/tests/decode.rs | 3 + tls_codec/fuzz/fuzz_targets/inverse.rs | 1 + tls_codec/src/lib.rs | 9 +- tls_codec/src/quic_vec.rs | 310 +++++++++++++++++++++++-- tls_codec/tests/decode.rs | 3 + tls_codec/tests/encode.rs | 3 + tls_codec/tests/serde_impls.rs | 134 +++++++++-- 10 files changed, 417 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21dceea0d..00f1739f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1746,6 +1746,7 @@ dependencies = [ "criterion", "serde", "serde_bytes", + "serde_json", "tls_codec_derive", "zeroize", ] diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml index cb1517ce7..32bfec919 100644 --- a/tls_codec/Cargo.toml +++ b/tls_codec/Cargo.toml @@ -23,6 +23,7 @@ serde_bytes = { version = "0.11.17", optional = true } [dev-dependencies] criterion = { version = "0.6", default-features = false } ciborium = "0.2.2" +serde_json = "1.0" [features] default = ["std"] diff --git a/tls_codec/benches/quic_vec.rs b/tls_codec/benches/quic_vec.rs index 18e8f4491..9181bbc07 100644 --- a/tls_codec/benches/quic_vec.rs +++ b/tls_codec/benches/quic_vec.rs @@ -1,3 +1,6 @@ +// The benches intentionally exercise the deprecated `VLBytes`. +#![allow(deprecated)] + use criterion::{BatchSize, Criterion}; use criterion::{criterion_group, criterion_main}; diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index 361773caa..e0270506e 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -1,4 +1,7 @@ #![cfg(feature = "std")] +// These tests intentionally exercise the deprecated `VLBytes` for backward +// compatibility coverage. +#![allow(deprecated)] use tls_codec::{ Deserialize, DeserializeBytes, Error, Serialize, Size, TlsSliceU16, TlsVecU8, TlsVecU16, TlsVecU32, VLBytes, diff --git a/tls_codec/fuzz/fuzz_targets/inverse.rs b/tls_codec/fuzz/fuzz_targets/inverse.rs index 3a0314222..56215282a 100644 --- a/tls_codec/fuzz/fuzz_targets/inverse.rs +++ b/tls_codec/fuzz/fuzz_targets/inverse.rs @@ -1,4 +1,5 @@ #![no_main] +#![allow(deprecated)] use libfuzzer_sys::fuzz_target; use tls_codec::{Deserialize, Serialize, Size, VLBytes}; diff --git a/tls_codec/src/lib.rs b/tls_codec/src/lib.rs index 3ee3a889e..89f688c59 100644 --- a/tls_codec/src/lib.rs +++ b/tls_codec/src/lib.rs @@ -49,8 +49,13 @@ pub use tls_vec::{ }; #[cfg(feature = "std")] -pub use quic_vec::{SecretVLBytes, rw as vlen}; -pub use quic_vec::{VLByteSlice, VLBytes}; +pub use quic_vec::{SecretVLByteVec, rw as vlen}; +#[cfg(feature = "std")] +#[allow(deprecated)] +pub use quic_vec::SecretVLBytes; +pub use quic_vec::{VLByteSlice, VLByteVec}; +#[allow(deprecated)] +pub use quic_vec::VLBytes; #[cfg(feature = "derive")] pub use tls_codec_derive::{ diff --git a/tls_codec/src/quic_vec.rs b/tls_codec/src/quic_vec.rs index 84b2bc43d..9ddc8e18d 100644 --- a/tls_codec/src/quic_vec.rs +++ b/tls_codec/src/quic_vec.rs @@ -11,6 +11,13 @@ //! to 30-bit values. //! This is in contrast to the default behaviour defined by RFC 9000 that allows //! up to 62-bit length values. + +// `VLBytes` and `SecretVLBytes` are kept around as deprecated types. The +// internal trait impls for them and their use as building blocks for other +// items in this module would otherwise emit deprecation warnings at every call +// site within the crate. +#![allow(deprecated)] + use super::alloc::vec::Vec; use core::fmt; @@ -233,12 +240,12 @@ macro_rules! impl_vl_bytes_generic { /// This is faster than the generic version. #[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] #[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[deprecated( + since = "0.4.3", + note = "Use `VLByteVec` instead. `VLBytes` does not produce a compact serde representation \ + of byte vectors. The serde format of `VLByteVec` is not compatible with `VLBytes`." +)] pub struct VLBytes { - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_bytes::serialize"))] - #[cfg_attr( - feature = "serde", - serde(deserialize_with = "serde_impl::de_vec_bytes_compat") - )] vec: Vec, } @@ -327,56 +334,181 @@ impl Size for &VLBytes { } } -#[cfg(feature = "serde")] -mod serde_impl { - use std::{fmt, vec::Vec}; +/// Variable-length encoded byte vector. +/// +/// Functionally equivalent to [`VLBytes`], but its `serde` representation uses +/// `serde_bytes` to serialize the contained byte vector as a byte blob via +/// `#[serde(transparent)]`. This produces a much more compact encoding for +/// `serde` formats that distinguish byte arrays from sequences of `u8` (e.g. +/// CBOR, MessagePack, bincode). +/// +/// While the `serde` format produced by `VLByteVec` is **not** compatible with +/// the format produced by [`VLBytes`], `VLByteVec`'s custom `Deserialize` impl +/// is backwards-compatible: it accepts both its own native bytes encoding and +/// the legacy [`VLBytes`] encoding (a struct with a `vec` field containing a +/// sequence of `u8`). This lets callers transparently migrate persisted +/// [`VLBytes`] data to `VLByteVec` for self-describing formats such as CBOR, +/// JSON or MessagePack. +#[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct VLByteVec { + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_bytes::serialize"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "serde_compat::deserialize_vlbytes_compat") + )] + vec: Vec, +} + +impl VLByteVec { + /// Generate a new variable-length byte vector. + pub fn new(vec: Vec) -> Self { + Self { vec } + } + + fn vec(&self) -> &[u8] { + &self.vec + } + + fn vec_mut(&mut self) -> &mut Vec { + &mut self.vec + } +} + +impl_vl_bytes_generic!(VLByteVec); + +#[cfg(feature = "std")] +impl Zeroize for VLByteVec { + fn zeroize(&mut self) { + self.vec.zeroize(); + } +} + +impl From for Vec { + fn from(b: VLByteVec) -> Self { + b.vec + } +} + +impl Size for VLByteVec { + #[inline(always)] + fn tls_serialized_len(&self) -> usize { + tls_serialize_bytes_len(self.as_slice()) + } +} +impl DeserializeBytes for VLByteVec { + #[inline(always)] + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (length, remainder) = ContentLength::tls_deserialize_bytes(bytes)?; + let length: usize = length.0.value().try_into()?; + + if length == 0 { + return Ok((Self::new(vec![]), remainder)); + } + + match remainder.get(..length).ok_or(Error::EndOfStream) { + Ok(vec) => Ok((Self { vec: vec.to_vec() }, &remainder[length..])), + Err(_e) => { + let remaining_len = remainder.len(); + if !cfg!(fuzzing) { + debug_assert_eq!( + remaining_len, length, + "Expected to read {length} bytes but {remaining_len} were read.", + ); + } + Err(Error::DecodingError(format!( + "{remaining_len} bytes were read but {length} were expected", + ))) + } + } + } +} + +impl Size for &VLByteVec { + #[inline(always)] + fn tls_serialized_len(&self) -> usize { + (*self).tls_serialized_len() + } +} + +#[cfg(feature = "serde")] +mod serde_compat { + use super::Vec; + use crate::alloc::string::String; + use core::fmt; use serde::{Deserializer, de}; - pub(super) fn de_vec_bytes_compat<'de, D>(deserializer: D) -> Result, D::Error> + /// Deserialize a `Vec` from either: + /// * a native byte blob (`VLByteVec`'s native encoding), or + /// * a sequence of `u8` (`VLByteVec`'s native encoding in `serde` formats + /// without a distinct byte type, e.g. JSON), or + /// * a `VLBytes`-shaped struct: a map with a `vec` field containing a + /// sequence of `u8` (the legacy [`super::VLBytes`] encoding). + /// + /// Uses `Deserializer::deserialize_any`, so it requires a self-describing + /// `serde` format. + pub(super) fn deserialize_vlbytes_compat<'de, D>( + deserializer: D, + ) -> Result, D::Error> where D: Deserializer<'de>, { - struct BytesOrSeq; + struct CompatVisitor; - impl<'de> de::Visitor<'de> for BytesOrSeq { + impl<'de> de::Visitor<'de> for CompatVisitor { type Value = Vec; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("either a byte blob or a sequence of u8") + f.write_str( + "a byte blob, a sequence of `u8`, or a struct with a `vec` field \ + containing a sequence of `u8`", + ) } - // New format (native bytes; e.g., CBOR/Bincode/Msgpack) - fn visit_bytes(self, v: &[u8]) -> Result - where - E: de::Error, - { + fn visit_bytes(self, v: &[u8]) -> Result { Ok(v.to_vec()) } - fn visit_byte_buf(self, v: Vec) -> Result - where - E: de::Error, - { + fn visit_byte_buf(self, v: Vec) -> Result { Ok(v) } - // Old format (seq of u8) fn visit_seq(self, mut seq: A) -> Result where A: de::SeqAccess<'de>, { - let mut out = Vec::new(); + let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0)); while let Some(b) = seq.next_element::()? { out.push(b); } Ok(out) } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut vec: Option> = None; + while let Some(key) = map.next_key::()? { + if key == "vec" { + if vec.is_some() { + return Err(de::Error::duplicate_field("vec")); + } + vec = Some(map.next_value::>()?); + } else { + let _: de::IgnoredAny = map.next_value()?; + } + } + vec.ok_or_else(|| de::Error::missing_field("vec")) + } } - deserializer.deserialize_any(BytesOrSeq) + deserializer.deserialize_any(CompatVisitor) } } + pub struct VLByteSlice<'a>(pub &'a [u8]); impl fmt::Debug for VLByteSlice<'_> { @@ -562,6 +694,36 @@ mod rw_bytes { } } + impl Serialize for VLByteVec { + #[inline(always)] + fn tls_serialize(&self, writer: &mut W) -> Result { + tls_serialize_bytes(writer, self.as_slice()) + } + } + + impl Serialize for &VLByteVec { + #[inline(always)] + fn tls_serialize(&self, writer: &mut W) -> Result { + (*self).tls_serialize(writer) + } + } + + impl Deserialize for VLByteVec { + fn tls_deserialize(bytes: &mut R) -> Result { + let length = ContentLength::tls_deserialize(bytes)?; + + if length.0.value() == 0 { + return Ok(Self::new(vec![])); + } + + let mut result = Self { + vec: vec![0u8; length.0.value().try_into()?], + }; + bytes.read_exact(result.vec.as_mut_slice())?; + Ok(result) + } + } + impl Serialize for &VLByteSlice<'_> { fn tls_serialize(&self, writer: &mut W) -> Result { tls_serialize_bytes(writer, self.0) @@ -585,6 +747,11 @@ mod secret_bytes { /// a [`Vec`]. #[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] #[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] + #[deprecated( + since = "0.4.3", + note = "Use `SecretVLByteVec` instead. The serde format of `SecretVLByteVec` is not \ + compatible with `SecretVLBytes`." + )] pub struct SecretVLBytes(VLBytes); impl SecretVLBytes { @@ -654,6 +821,92 @@ mod secret_bytes { #[cfg(feature = "std")] pub use secret_bytes::SecretVLBytes; +#[cfg(feature = "std")] +mod secret_byte_vec { + use super::*; + use crate::{Deserialize, Serialize}; + + /// A wrapper struct around [`VLByteVec`] that implements [`ZeroizeOnDrop`]. + /// It behaves just like [`VLByteVec`], except that it doesn't allow + /// conversion into a [`Vec`]. + /// + /// Like [`VLByteVec`], `SecretVLByteVec` produces a different `serde` format + /// than the deprecated [`SecretVLBytes`], but its `Deserialize` impl is + /// backwards-compatible: it accepts both its own native encoding and the + /// legacy [`SecretVLBytes`] encoding (a struct with a `vec` field + /// containing a sequence of `u8`) for self-describing `serde` formats. + #[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] + pub struct SecretVLByteVec(VLByteVec); + + impl SecretVLByteVec { + /// Generate a new variable-length byte vector that implements + /// [`ZeroizeOnDrop`]. + pub fn new(vec: Vec) -> Self { + Self(VLByteVec { vec }) + } + + fn vec(&self) -> &[u8] { + &self.0.vec + } + + fn vec_mut(&mut self) -> &mut Vec { + &mut self.0.vec + } + } + + impl_vl_bytes_generic!(SecretVLByteVec); + + impl Zeroize for SecretVLByteVec { + fn zeroize(&mut self) { + self.0.zeroize(); + } + } + + impl Drop for SecretVLByteVec { + fn drop(&mut self) { + self.zeroize(); + } + } + + impl ZeroizeOnDrop for SecretVLByteVec {} + + impl Size for SecretVLByteVec { + fn tls_serialized_len(&self) -> usize { + self.0.tls_serialized_len() + } + } + + impl DeserializeBytes for SecretVLByteVec { + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> + where + Self: Sized, + { + let (bytes, remainder) = VLByteVec::tls_deserialize_bytes(bytes)?; + Ok((Self(bytes), remainder)) + } + } + + impl Serialize for SecretVLByteVec { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.0.tls_serialize(writer) + } + } + + impl Deserialize for SecretVLByteVec { + fn tls_deserialize(bytes: &mut R) -> Result + where + Self: Sized, + { + Ok(Self(VLByteVec::tls_deserialize(bytes)?)) + } + } +} + +#[cfg(feature = "std")] +pub use secret_byte_vec::SecretVLByteVec; + #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for VLBytes { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { @@ -668,6 +921,15 @@ impl<'a> Arbitrary<'a> for VLBytes { } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for VLByteVec { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let mut vec = Vec::arbitrary(u)?; + vec.truncate(ContentLength::MAX as usize); + Ok(Self { vec }) + } +} + #[cfg(feature = "std")] #[cfg(test)] mod test { diff --git a/tls_codec/tests/decode.rs b/tls_codec/tests/decode.rs index 8beac3874..e6b389e86 100644 --- a/tls_codec/tests/decode.rs +++ b/tls_codec/tests/decode.rs @@ -1,4 +1,7 @@ #![cfg(feature = "std")] +// These tests intentionally exercise the deprecated `VLBytes` for backward +// compatibility coverage. +#![allow(deprecated)] use tls_codec::{ Error, Serialize, Size, TlsByteSliceU16, TlsByteVecU8, TlsByteVecU16, TlsSliceU16, TlsVecU8, diff --git a/tls_codec/tests/encode.rs b/tls_codec/tests/encode.rs index 1d04de751..94cda264f 100644 --- a/tls_codec/tests/encode.rs +++ b/tls_codec/tests/encode.rs @@ -1,4 +1,7 @@ #![cfg(feature = "std")] +// These tests intentionally exercise the deprecated `VLBytes` for backward +// compatibility coverage. +#![allow(deprecated)] use tls_codec::{Serialize, TlsVecU16, TlsVecU24, U24, VLByteSlice, VLBytes}; diff --git a/tls_codec/tests/serde_impls.rs b/tls_codec/tests/serde_impls.rs index c9292af2e..8aba78987 100644 --- a/tls_codec/tests/serde_impls.rs +++ b/tls_codec/tests/serde_impls.rs @@ -1,35 +1,119 @@ #![cfg(feature = "serde")] +#![allow(deprecated)] -use tls_codec::VLBytes; +use tls_codec::{SecretVLByteVec, VLByteVec, VLBytes}; -// Old VLBytes without serde bytes serialization -#[derive(serde::Serialize, serde::Deserialize)] -struct OldVLBytes { - vec: Vec, +#[test] +fn vlbytes_serde_roundtrip() { + // Verify that `VLBytes` is identical after a serde roundtrip. This guards + // against changes to the `serde` representation of `VLBytes`, which would + // break callers that use it as (part of) a map key or otherwise rely on + // its default `Vec` representation. + for value in [ + VLBytes::from(vec![]), + VLBytes::from(vec![0u8]), + VLBytes::from(vec![0u8, 1, 2, 3]), + VLBytes::from(vec![0xAA; 1024]), + ] { + let mut buf = Vec::new(); + ciborium::into_writer(&value, &mut buf).unwrap(); + let roundtripped: VLBytes = ciborium::from_reader(buf.as_slice()).unwrap(); + assert_eq!(value, roundtripped); + } +} + +#[test] +fn vlbytevec_serde_roundtrip() { + for value in [ + VLByteVec::from(vec![]), + VLByteVec::from(vec![0u8]), + VLByteVec::from(vec![0u8, 1, 2, 3]), + VLByteVec::from(vec![0xAA; 1024]), + ] { + let mut buf = Vec::new(); + ciborium::into_writer(&value, &mut buf).unwrap(); + let roundtripped: VLByteVec = ciborium::from_reader(buf.as_slice()).unwrap(); + assert_eq!(value, roundtripped); + } +} + +#[test] +fn secret_vlbytevec_serde_roundtrip() { + for value in [ + SecretVLByteVec::new(vec![]), + SecretVLByteVec::new(vec![0u8, 1, 2, 3]), + SecretVLByteVec::new(vec![0xAA; 1024]), + ] { + let mut buf = Vec::new(); + ciborium::into_writer(&value, &mut buf).unwrap(); + let roundtripped: SecretVLByteVec = ciborium::from_reader(buf.as_slice()).unwrap(); + assert_eq!(value, roundtripped); + } +} + +#[test] +fn vlbytevec_uses_compact_serde_bytes_encoding() { + // `VLByteVec` uses `serde_bytes` via `#[serde(transparent)]`, so it should + // produce a strictly smaller CBOR encoding than `VLBytes` (which serializes + // as a struct wrapping a sequence of `u8`). + let payload = vec![0xAAu8; 128]; + let vl_bytes = VLBytes::from(payload.clone()); + let vl_byte_vec = VLByteVec::from(payload); + + let mut vl_bytes_buf = Vec::new(); + ciborium::into_writer(&vl_bytes, &mut vl_bytes_buf).unwrap(); + let mut vl_byte_vec_buf = Vec::new(); + ciborium::into_writer(&vl_byte_vec, &mut vl_byte_vec_buf).unwrap(); + + assert!(vl_byte_vec_buf.len() < vl_bytes_buf.len()); } -impl From for OldVLBytes { - fn from(v: VLBytes) -> Self { - OldVLBytes { vec: v.into() } +#[test] +fn vlbytevec_deserializes_legacy_vlbytes_format() { + // Encoded `VLBytes` data must remain readable by `VLByteVec`'s custom + // deserializer. We exercise CBOR (native byte type) and JSON (no native + // byte type, falls back to a sequence of `u8`). + for payload in [ + Vec::::new(), + vec![0u8], + vec![0u8, 1, 2, 3, 4, 5], + vec![0xAA; 1024], + ] { + let vl_bytes = VLBytes::from(payload.clone()); + + // CBOR + let mut cbor_buf = Vec::new(); + ciborium::into_writer(&vl_bytes, &mut cbor_buf).unwrap(); + let from_cbor: VLByteVec = ciborium::from_reader(cbor_buf.as_slice()).unwrap(); + assert_eq!(from_cbor.as_slice(), payload.as_slice()); + + // JSON + let json = serde_json::to_string(&vl_bytes).unwrap(); + let from_json: VLByteVec = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json.as_slice(), payload.as_slice()); } } #[test] -fn serde_impls() { - let value = VLBytes::from(vec![32; 128]); - let old_value: OldVLBytes = value.clone().into(); - let mut new_serialized = Vec::new(); - ciborium::into_writer(&value, &mut new_serialized).unwrap(); - let mut old_serialized = Vec::new(); - ciborium::into_writer(&old_value, &mut old_serialized).unwrap(); - - // Serialization format has changed - assert_ne!(new_serialized, old_serialized); - assert!(new_serialized.len() < old_serialized.len()); - - // We should be able to deserialize both into the new format - let deserialized: VLBytes = ciborium::from_reader(new_serialized.as_slice()).unwrap(); - let old_deserialized: VLBytes = ciborium::from_reader(old_serialized.as_slice()).unwrap(); - - assert_eq!(deserialized, old_deserialized); +fn secret_vlbytevec_deserializes_legacy_secret_vlbytes_format() { + use tls_codec::SecretVLBytes; + + for payload in [ + Vec::::new(), + vec![0u8, 1, 2, 3], + vec![0xAA; 1024], + ] { + let secret_vl_bytes = SecretVLBytes::new(payload.clone()); + + // CBOR + let mut cbor_buf = Vec::new(); + ciborium::into_writer(&secret_vl_bytes, &mut cbor_buf).unwrap(); + let from_cbor: SecretVLByteVec = ciborium::from_reader(cbor_buf.as_slice()).unwrap(); + assert_eq!(from_cbor.as_slice(), payload.as_slice()); + + // JSON + let json = serde_json::to_string(&secret_vl_bytes).unwrap(); + let from_json: SecretVLByteVec = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json.as_slice(), payload.as_slice()); + } } From a98263c5981cbe10ed8f39d8b88493910ce96120 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 29 Apr 2026 14:05:07 +0200 Subject: [PATCH 2/3] adjust changelog --- tls_codec/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls_codec/CHANGELOG.md b/tls_codec/CHANGELOG.md index 93c3277f0..2e70a71fb 100644 --- a/tls_codec/CHANGELOG.md +++ b/tls_codec/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- [#2022](https://github.com/RustCrypto/formats/pull/2022) Optimize serde serialization of byte vectors using `serde_bytes`. Deserialization of the old serialized format will still work, but newly serialized instances of `VLBytes` will have the new format. +- [#2322](https://github.com/RustCrypto/formats/pull/2322): Add `VLByteVec` and `SecretVLByteVec`, which are `#[serde(transparent)]` wrappers serializing via `serde_bytes`. They produce a much more compact representation in `serde` formats that distinguish byte arrays from sequences of `u8` (e.g. CBOR, MessagePack, bincode). Their `serde` output is not compatible with `VLBytes` / `SecretVLBytes`, but their `Deserialize` impls are backwards-compatible: in self-describing `serde` formats they also accept the legacy `VLBytes` / `SecretVLBytes` encoding (a struct with a `vec` field containing a sequence of `u8`). Deprecate `VLBytes` and `SecretVLBytes` in favour of `VLByteVec` and `SecretVLByteVec`. - [#1656](https://github.com/RustCrypto/formats/pull/1656) Add `TlsVarInt` type for variable-length integers. ## 0.4.2 From c4437458b063d313c5274ef44fb4a31f2535c6ee Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 29 Apr 2026 14:09:18 +0200 Subject: [PATCH 3/3] cargo fmt --- tls_codec/src/lib.rs | 6 +++--- tls_codec/src/quic_vec.rs | 4 +--- tls_codec/tests/serde_impls.rs | 6 +----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tls_codec/src/lib.rs b/tls_codec/src/lib.rs index 89f688c59..cdd6cc155 100644 --- a/tls_codec/src/lib.rs +++ b/tls_codec/src/lib.rs @@ -48,14 +48,14 @@ pub use tls_vec::{ TlsVecU24, TlsVecU32, }; -#[cfg(feature = "std")] -pub use quic_vec::{SecretVLByteVec, rw as vlen}; #[cfg(feature = "std")] #[allow(deprecated)] pub use quic_vec::SecretVLBytes; -pub use quic_vec::{VLByteSlice, VLByteVec}; #[allow(deprecated)] pub use quic_vec::VLBytes; +#[cfg(feature = "std")] +pub use quic_vec::{SecretVLByteVec, rw as vlen}; +pub use quic_vec::{VLByteSlice, VLByteVec}; #[cfg(feature = "derive")] pub use tls_codec_derive::{ diff --git a/tls_codec/src/quic_vec.rs b/tls_codec/src/quic_vec.rs index 9ddc8e18d..d477dd9fb 100644 --- a/tls_codec/src/quic_vec.rs +++ b/tls_codec/src/quic_vec.rs @@ -449,9 +449,7 @@ mod serde_compat { /// /// Uses `Deserializer::deserialize_any`, so it requires a self-describing /// `serde` format. - pub(super) fn deserialize_vlbytes_compat<'de, D>( - deserializer: D, - ) -> Result, D::Error> + pub(super) fn deserialize_vlbytes_compat<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { diff --git a/tls_codec/tests/serde_impls.rs b/tls_codec/tests/serde_impls.rs index 8aba78987..d6f5b8754 100644 --- a/tls_codec/tests/serde_impls.rs +++ b/tls_codec/tests/serde_impls.rs @@ -98,11 +98,7 @@ fn vlbytevec_deserializes_legacy_vlbytes_format() { fn secret_vlbytevec_deserializes_legacy_secret_vlbytes_format() { use tls_codec::SecretVLBytes; - for payload in [ - Vec::::new(), - vec![0u8, 1, 2, 3], - vec![0xAA; 1024], - ] { + for payload in [Vec::::new(), vec![0u8, 1, 2, 3], vec![0xAA; 1024]] { let secret_vl_bytes = SecretVLBytes::new(payload.clone()); // CBOR