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 { 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, 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, 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, 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, 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::from_bytes_with(bytes, &self.type_map) } pub fn negotiate(&self, client_versions: &[Version]) -> Option { 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(), }) ); } }