126 lines
4 KiB
Rust
126 lines
4 KiB
Rust
use mtp_common::CodecError;
|
|
use mtp_type_map::{PROTOCOL_VERSION, TypeMap, Version};
|
|
|
|
use crate::CommunicationValue;
|
|
use crate::EncodeLimits;
|
|
|
|
pub use mtp_type_map::Registry;
|
|
|
|
/*
|
|
* A version-aware codec that uses a multi-version registry to resolve
|
|
* the correct TypeMap for encoding and decoding operations.
|
|
*/
|
|
#[derive(Clone, Debug)]
|
|
pub struct VersionedCodec {
|
|
registry: Registry,
|
|
type_map: TypeMap,
|
|
}
|
|
|
|
impl VersionedCodec {
|
|
pub fn new(registry: Registry) -> Self {
|
|
let type_map = registry
|
|
.latest()
|
|
.cloned()
|
|
.unwrap_or_else(|| TypeMap::new(PROTOCOL_VERSION));
|
|
Self { registry, type_map }
|
|
}
|
|
|
|
/// Create a codec bound to a negotiated protocol version.
|
|
pub fn for_version(registry: Registry, version: Version) -> Option<Self> {
|
|
let type_map = registry.get(&version)?.clone();
|
|
Some(Self { registry, type_map })
|
|
}
|
|
|
|
/// Return the type map used by this codec.
|
|
pub fn type_map(&self) -> &TypeMap {
|
|
&self.type_map
|
|
}
|
|
|
|
/// Return the protocol version used by this codec.
|
|
pub fn version(&self) -> &Version {
|
|
&self.type_map.version
|
|
}
|
|
|
|
/// Encode a value using the codec's negotiated framing rules.
|
|
pub fn encode(&self, value: &CommunicationValue) -> Result<Vec<u8>, CodecError> {
|
|
self.encode_with_limits(value, EncodeLimits::default())
|
|
}
|
|
|
|
/// Encode using an explicit output/resource limit after verifying the
|
|
/// value belongs to this codec's negotiated type map.
|
|
pub fn encode_with_limits(
|
|
&self,
|
|
value: &CommunicationValue,
|
|
limits: EncodeLimits,
|
|
) -> Result<Vec<u8>, CodecError> {
|
|
let value_map = value.type_map().ok_or(CodecError::MissingTypeMap)?;
|
|
if value_map.version != self.type_map.version {
|
|
return Err(CodecError::TypeMapMismatch {
|
|
expected: self.type_map.version.to_string(),
|
|
actual: value_map.version.to_string(),
|
|
});
|
|
}
|
|
value.to_bytes_with_limits(limits)
|
|
}
|
|
|
|
/// Explicitly migrate a clear frame to this codec's negotiated type map
|
|
/// before encoding it.
|
|
pub fn encode_migrating(&self, value: &CommunicationValue) -> Result<Vec<u8>, CodecError> {
|
|
self.encode_migrating_with_limits(value, EncodeLimits::default())
|
|
}
|
|
|
|
/// Explicitly migrate and encode with bounded traversal/output.
|
|
pub fn encode_migrating_with_limits(
|
|
&self,
|
|
value: &CommunicationValue,
|
|
limits: EncodeLimits,
|
|
) -> Result<Vec<u8>, CodecError> {
|
|
value
|
|
.migrate_with_limits(&self.type_map, limits)?
|
|
.to_bytes_with_limits(limits)
|
|
}
|
|
|
|
/// Decode a frame and retain the negotiated type map for typed access.
|
|
pub fn decode(&self, bytes: &[u8]) -> Result<CommunicationValue, CodecError> {
|
|
CommunicationValue::from_bytes_with(bytes, &self.type_map)
|
|
}
|
|
|
|
pub fn negotiate(&self, client_versions: &[Version]) -> Option<Version> {
|
|
self.registry.negotiate(client_versions)
|
|
}
|
|
|
|
pub fn registry(&self) -> &Registry {
|
|
&self.registry
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::DataValue;
|
|
use mtp_type_map::{CommunicationType, Version};
|
|
|
|
#[test]
|
|
fn encode_rejects_a_value_from_another_negotiated_map() {
|
|
let mut registry = Registry::new();
|
|
let version_a = Version::new(3, 0);
|
|
let version_b = Version::new(4, 0);
|
|
registry.register(TypeMap::new(version_a.clone()));
|
|
registry.register(TypeMap::new(version_b.clone()));
|
|
|
|
let codec = VersionedCodec::for_version(registry, version_b).expect("codec version");
|
|
let value = CommunicationValue::new_with_type_map(
|
|
CommunicationType::Ping,
|
|
&TypeMap::new(version_a.clone()),
|
|
)
|
|
.with_payload(DataValue::Null);
|
|
|
|
assert_eq!(
|
|
codec.encode(&value),
|
|
Err(CodecError::TypeMapMismatch {
|
|
expected: "4.0".into(),
|
|
actual: "3.0".into(),
|
|
})
|
|
);
|
|
}
|
|
}
|