mod compatibility;
mod header;
mod version;
pub use compatibility::{can_migrate, check_compatibility, CompatibilityLevel};
#[cfg(feature = "alloc")]
pub use compatibility::migration_path;
pub use header::{VersionedHeader, VERSIONED_MAGIC};
pub use version::Version;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use crate::Error;
use crate::Result;
#[cfg(feature = "alloc")]
pub fn encode_versioned(data: &[u8], version: Version) -> Result<alloc::vec::Vec<u8>> {
let header = VersionedHeader::new(version);
let header_bytes = header.to_bytes();
let mut output = alloc::vec::Vec::with_capacity(header_bytes.len() + data.len());
output.extend_from_slice(&header_bytes);
output.extend_from_slice(data);
Ok(output)
}
#[cfg(feature = "alloc")]
pub fn decode_versioned(data: &[u8]) -> Result<(alloc::vec::Vec<u8>, Version)> {
let header = VersionedHeader::from_bytes(data)?;
let payload_start = header.header_size();
if data.len() < payload_start {
return Err(Error::UnexpectedEnd {
additional: payload_start - data.len(),
});
}
let payload = data[payload_start..].to_vec();
Ok((payload, header.version()))
}
#[cfg(feature = "alloc")]
pub fn decode_versioned_with_check(
data: &[u8],
expected: Version,
min_compatible: Option<Version>,
) -> Result<(alloc::vec::Vec<u8>, Version, CompatibilityLevel)> {
let (payload, version) = decode_versioned(data)?;
let compat = check_compatibility(version, expected, min_compatible);
if matches!(compat, CompatibilityLevel::Incompatible) {
return Err(Error::InvalidData {
message: "version incompatible",
});
}
Ok((payload, version, compat))
}
pub fn is_versioned(data: &[u8]) -> bool {
data.len() >= VERSIONED_MAGIC.len() && data[..VERSIONED_MAGIC.len()] == VERSIONED_MAGIC
}
pub fn extract_version(data: &[u8]) -> Result<Version> {
let header = VersionedHeader::from_bytes(data)?;
Ok(header.version())
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use super::*;
#[cfg(feature = "alloc")]
#[test]
fn test_versioned_roundtrip() {
let data = b"Hello, World!";
let version = Version::new(1, 2, 3);
let encoded = encode_versioned(data, version).expect("encode failed");
let (decoded, ver) = decode_versioned(&encoded).expect("decode failed");
assert_eq!(data.as_slice(), decoded.as_slice());
assert_eq!(version, ver);
}
#[cfg(feature = "alloc")]
#[test]
fn test_is_versioned() {
let data = b"Hello, World!";
let version = Version::new(1, 0, 0);
assert!(!is_versioned(data));
let encoded = encode_versioned(data, version).expect("encode failed");
assert!(is_versioned(&encoded));
}
#[cfg(feature = "alloc")]
#[test]
fn test_extract_version() {
let data = b"test";
let version = Version::new(2, 5, 10);
let encoded = encode_versioned(data, version).expect("encode failed");
let extracted = extract_version(&encoded).expect("extract failed");
assert_eq!(version, extracted);
}
#[cfg(feature = "alloc")]
#[test]
fn test_compatibility_check() {
let data = b"test";
let data_version = Version::new(1, 5, 0);
let current = Version::new(1, 6, 0);
let min_compat = Some(Version::new(1, 0, 0));
let encoded = encode_versioned(data, data_version).expect("encode failed");
let (_, ver, compat) =
decode_versioned_with_check(&encoded, current, min_compat).expect("decode failed");
assert_eq!(ver, data_version);
assert!(matches!(
compat,
CompatibilityLevel::Compatible | CompatibilityLevel::CompatibleWithWarnings
));
}
#[cfg(feature = "alloc")]
#[test]
fn test_incompatible_version() {
let data = b"test";
let data_version = Version::new(2, 0, 0); let current = Version::new(1, 0, 0);
let encoded = encode_versioned(data, data_version).expect("encode failed");
let result = decode_versioned_with_check(&encoded, current, None);
assert!(result.is_err());
}
}