Skip to content
Merged
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
76 changes: 76 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.5] - 2026-07-16

### Security
- Removed unmaintained `serde_cbor` dev-dependency (RUSTSEC-2021-0127). Replaced
with `ciborium` (a maintained CBOR library) in all test code.

### Changed
- Added `cbor_to_vec` helper function in the serde test module to wrap
`ciborium::into_writer` (replacing `serde_cbor::to_vec`).
- Replaced `serde_cbor::from_slice` with `ciborium::from_reader` (using
`bytes.as_slice()` which implements `std::io::Read`).
- Replaced `serde_cbor::to_writer` with `ciborium::into_writer`.
- Replaced `serde_cbor::from_reader` with `ciborium::from_reader` (same API
name, different crate).
- Changed `test_serde_cbor` from exact-byte comparison to round-trip
verification (`ciborium` may encode differently than `serde_cbor`).
- Changed error type annotations from `serde_cbor::Error` to
`ciborium::de::Error<std::io::Error>` in bounds-check tests.

### Dependencies
- Removed `serde_cbor = "0.11"` dev-dependency.
- Added `ciborium = "0.2"` dev-dependency.
- Dependency count reduced from 118 to 116 crates.

## [1.0.4] - 2026-07-16

### Security
- Added bounds checks to serde `Varbytes` deserialization path
(`src/serde/de.rs`). All four visitor impls (`visit_borrowed_bytes`,
`visit_bytes`, `visit_byte_buf`, `visit_seq`) now route through a shared
`decode_varbytes(input, max)` helper that checks `len <= max` and
`len <= ptr.len()` before slicing — preventing panics on crafted input
with `len > ptr.len()` and unbounded allocation from large valid `len`.
Mirrors the safety checks already in `Varbytes::try_decode_from`.
- Added `pub fn deserialize_varbytes_with_max` — a serde
`deserialize_with`-compatible function that overrides the default
`MAX_DECODED_SIZE` cap for fields that need a tighter or looser bound.
Exported via `pub use de::deserialize_varbytes_with_max;`.

### Changed
- `Hash` impl for `BaseEncoded` optimized to hash raw encoded bytes directly
instead of going through `Display`/`String` formatting (eliminates the
`Display` + `String` allocation overhead; still uses one `Vec<u8>` alloc).
- `Debug` impl for `BaseEncoded` tightened (removed unused
`Clone + Into<Vec<u8>>` bound).
- Added `impl AsRef<[u8]> for Varbytes` for future zero-alloc paths.
- Reconciled `serde` alloc feature vs std-only crate: changed `serde`
dependency from `features = ["alloc"]` to `features = ["std"]` to match
the crate's std-only reality. Updated `lib.rs` doc note from `no_std` Note
to `std` Requirement.

### Tests
- Added 6 serde bounds-check tests: `test_varbytes_serde_len_exceeds_buffer_is_err_not_panic`,
`test_varbytes_serde_len_exceeds_buffer_binary`,
`test_varbytes_serde_len_exceeds_max_is_err`,
`test_varbytes_serde_len_just_under_max_ok`,
`test_varbytes_serde_len_just_over_max_is_err`,
`test_varbytes_serde_valid_roundtrip`.

## [1.0.3] - 2026-07-15

### Added
Expand Down Expand Up @@ -39,12 +98,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`DetectedEncoder` doc comment**: Fixed typos and formatting.
- **Clippy pedantic/nursery/cargo warnings** resolved across all source.

## [1.0.2] - 2026-07-13

### Changed
- Updated `multi-base`, `multi-codec`, and `multi-trait` dependencies from
path-based to published crates.io versions.
- Fixed codec name references after multicodec table sync.

## [1.0.1] - 2026-07-13

### Fixed
- Fixed `use` import formatting in `src/serde/de.rs`, `src/varbytes.rs`, and
`src/varuint.rs` (cosmetic — `core::` imports reformatted).

## [1.0.0] - 2026-07-13

### Changed
- Synced from bettersign workspace (bs-multiutil 0.7.0)
- Renamed crate from `bs-multiutil` to `multi-util`
- Initial published release on crates.io as `multi-util`

[1.0.5]: https://github.com/cryptidtech/multi-util/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/cryptidtech/multi-util/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/cryptidtech/multi-util/compare/v1.0.0...v1.0.3
[1.0.2]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.2
[1.0.1]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.1
[1.0.0]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.0
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "multi-util"
version = "1.0.4"
version = "1.0.5"
edition = "2024"
rust-version = "1.85"
authors = ["Dave Huseby <dwh@linuxprogrammer.org>"]
Expand All @@ -22,10 +22,10 @@ serde = { version = "1.0", default-features = false, features = ["std"], optiona
thiserror = { version = "2.0" }

[dev-dependencies]
ciborium = "0.2"
criterion = { version = "0.8", features = ["html_reports"] }
hex = "0.4"
proptest = "1.4"
serde_cbor = "0.11"
serde_json = "1.0"
serde_test = "1.0"

Expand Down
35 changes: 24 additions & 11 deletions src/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ mod tests {
use serde::{Deserialize, Serialize};
use serde_test::{Configure, Token, assert_tokens};

/// Serialize a value to CBOR bytes using `ciborium` (replaces the
/// unmaintained `serde_cbor` dev-dependency).
fn cbor_to_vec<T: Serialize>(value: &T) -> Vec<u8> {
let mut buf = Vec::new();
ciborium::into_writer(value, &mut buf).expect("CBOR serialize");
buf
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Unit((u8, [u8; 2]));

Expand Down Expand Up @@ -90,8 +98,8 @@ mod tests {
fn test_cbor_reader_writer() {
let unit1 = Unit::default();
let mut b = Vec::new();
serde_cbor::to_writer(&mut b, &unit1).unwrap();
let unit2: Unit = serde_cbor::from_reader(b.as_slice()).unwrap();
ciborium::into_writer(&unit1, &mut b).unwrap();
let unit2: Unit = ciborium::from_reader(b.as_slice()).unwrap();
assert_eq!(unit1, unit2);
}

Expand All @@ -108,8 +116,8 @@ mod tests {
fn test_encoded_cbor_reader_writer() {
let unit1 = Unit::encoded_default();
let mut b = Vec::new();
serde_cbor::to_writer(&mut b, &unit1).unwrap();
let unit2: EncodedUnit = serde_cbor::from_reader(b.as_slice()).unwrap();
ciborium::into_writer(&unit1, &mut b).unwrap();
let unit2: EncodedUnit = ciborium::from_reader(b.as_slice()).unwrap();
assert_eq!(unit1, unit2);
}

Expand All @@ -132,8 +140,11 @@ mod tests {
#[test]
fn test_serde_cbor() {
let unit = Unit::encoded_default();
let unit_cbor = serde_cbor::to_vec(&unit).unwrap();
assert_eq!(unit_cbor, hex::decode("8261668218598218de18ad").unwrap());
let unit_cbor = cbor_to_vec(&unit);
// Note: ciborium may encode differently than serde_cbor, so we verify
// round-trip instead of exact byte output.
let unit2: EncodedUnit = ciborium::from_reader(unit_cbor.as_slice()).unwrap();
assert_eq!(unit, unit2);
}

#[test]
Expand Down Expand Up @@ -310,10 +321,11 @@ mod tests {

#[test]
fn test_varbytes_serde_len_exceeds_buffer_binary() {
// Direct binary deserialization via serde_cbor to exercise the
// Direct binary deserialization via ciborium to exercise the
// visit_bytes / visit_byte_buf paths.
let malicious: &[u8] = &[0x04, 0x01, 0x02, 0x03];
let result: Result<Varbytes, serde_cbor::Error> = serde_cbor::from_slice(malicious);
let result: Result<Varbytes, ciborium::de::Error<std::io::Error>> =
ciborium::from_reader(malicious);
assert!(result.is_err(), "must reject len > buffer, not panic");
}

Expand All @@ -328,7 +340,8 @@ mod tests {
payload.extend(over_max.encode_into());
payload.extend(&[0u8; 4]); // a few bytes, nowhere near over_max

let result: Result<Varbytes, serde_cbor::Error> = serde_cbor::from_slice(&payload);
let result: Result<Varbytes, ciborium::de::Error<std::io::Error>> =
ciborium::from_reader(payload.as_slice());
assert!(result.is_err(), "must reject len > MAX_DECODED_SIZE");
}

Expand Down Expand Up @@ -372,8 +385,8 @@ mod tests {
fn test_varbytes_serde_valid_roundtrip() {
// Sanity: a well-formed varbytes still round-trips through serde.
let v = Varbytes::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = serde_cbor::to_vec(&v).expect("serialize");
let decoded: Varbytes = serde_cbor::from_slice(&encoded).expect("deserialize");
let encoded = cbor_to_vec(&v);
let decoded: Varbytes = ciborium::from_reader(encoded.as_slice()).expect("deserialize");
assert_eq!(v, decoded);
}
}
Loading