use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::collections::BTreeMap; use std::fmt; use std::io::{Cursor, Read}; use crate::data_value::{DataKind, DataValue}; use crate::rand_u32; use mtp_common::CodecError; use mtp_type_map::{ CommunicationType, CommunicationTypeId, DataType, DataTypeId, PROTOCOL_VERSION, TypeMap, communication_type_name, data_type_name, }; #[cfg(feature = "crypto")] use mtp_crypto::{PublicKeyBundle, SigAlgorithm, SignatureScheme}; const FLAG_HAS_SENDER: u8 = 0b0000_0001; const FLAG_HAS_RECEIVER: u8 = 0b0000_0010; const FLAG_HAS_ID: u8 = 0b0000_0100; const FLAG_ENCRYPTED: u8 = 0b0000_1000; const FLAG_SIGNED: u8 = 0b0001_0000; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommunicationValue { id: u32, comm_type: CommunicationTypeId, sender: u64, receiver: u64, data: BTreeMap, type_map: Option, #[cfg(feature = "crypto")] frame_signature: Option<(u8, Vec)>, } impl CommunicationValue { #[must_use] pub fn new(comm_type: CommunicationType) -> Self { let tm = TypeMap::new(PROTOCOL_VERSION); let id = comm_type.to_id(&tm); Self { id: rand_u32(), comm_type: id, sender: 0, receiver: 0, data: BTreeMap::new(), type_map: Some(tm), #[cfg(feature = "crypto")] frame_signature: None, } } #[cfg(feature = "registry")] #[must_use] pub fn from_comm(comm_type: CommunicationType, tm: &TypeMap) -> Self { let id = comm_type.to_id(tm); Self { id: rand_u32(), comm_type: id, sender: 0, receiver: 0, data: BTreeMap::new(), type_map: Some(tm.clone()), #[cfg(feature = "crypto")] frame_signature: None, } } #[must_use] pub fn with_id(mut self, p0: u32) -> Self { self.id = p0; self } pub fn get_id(&self) -> u32 { self.id } #[must_use] pub fn with_sender(mut self, sender: u64) -> Self { self.sender = sender; self } pub fn get_sender(&self) -> u64 { self.sender } #[must_use] pub fn with_receiver(mut self, receiver: u64) -> Self { self.receiver = receiver; self } pub fn get_receiver(&self) -> u64 { self.receiver } pub fn get_type(&self) -> CommunicationTypeId { self.comm_type } #[must_use] pub fn add_data(mut self, data: DataTypeId, value: DataValue) -> Self { self.data.insert(data, value); self } #[cfg(feature = "registry")] #[must_use] pub fn add_typed(mut self, data: DataType, tm: &TypeMap, value: DataValue) -> Self { self.data.insert(data.to_id(tm), value); self } #[must_use] pub fn add_typed_default(mut self, data: DataType, value: DataValue) -> Self { let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest); self.data.insert(data.to_id(&tm), value); self } pub fn get_data(&self, data_type: DataType) -> &DataValue { let tm_owned; let tm = match &self.type_map { Some(tm) => tm, None => { tm_owned = TypeMap::latest(); &tm_owned } }; match tm.data_id_enum(data_type) { Some(raw_id) => self .data .get(&DataTypeId(raw_id)) .unwrap_or(&DataValue::Null), None => &DataValue::Null, } } pub fn get_data_opt(&self, data_type: DataType) -> Option<&DataValue> { let tm_owned; let tm = match &self.type_map { Some(tm) => tm, None => { tm_owned = TypeMap::latest(); &tm_owned } }; let raw_id = tm.data_id_enum(data_type)?; self.data.get(&DataTypeId(raw_id)) } pub fn has_data(&self, data_type: DataType) -> Option { self.get_data_opt(data_type).map(|v| v.kind()) } pub fn get_comm_type_enum(&self) -> Option { let tm_owned; let tm = match &self.type_map { Some(tm) => tm, None => { tm_owned = TypeMap::latest(); &tm_owned } }; tm.comm_enum_id(self.comm_type.0) } pub fn data(&self) -> &BTreeMap { &self.data } pub fn data_len(&self) -> usize { self.data.len() } // ── type checks ────────────────────────────────────────────────────────── pub fn is_type(&self, comm_type: CommunicationType) -> bool { self.get_comm_type_enum() == Some(comm_type) } pub fn get_type_name(&self) -> Option<&'static str> { communication_type_name(self.comm_type.0) } // ── mutation ───────────────────────────────────────────────────────────── pub fn set_data(&mut self, data_type: DataType, value: DataValue) { let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest); if let Some(raw_id) = tm.data_id_enum(data_type) { self.data.insert(DataTypeId(raw_id), value); } } #[must_use] pub fn with_data(mut self, data_type: DataType, value: DataValue) -> Self { self.set_data(data_type, value); self } pub fn remove_data(&mut self, data_type: DataType) -> Option { let tm_owned; let tm = match &self.type_map { Some(tm) => tm, None => { tm_owned = TypeMap::latest(); &tm_owned } }; let raw_id = tm.data_id_enum(data_type)?; self.data.remove(&DataTypeId(raw_id)) } #[must_use] pub fn reply_to(&self, comm_type: CommunicationType) -> Self { Self::new(comm_type) .with_sender(self.receiver) .with_receiver(self.sender) } pub fn merge(&mut self, other: &CommunicationValue) { for (id, value) in &other.data { self.data.insert(*id, value.clone()); } } // ── typed iteration ────────────────────────────────────────────────────── pub fn iter_typed_data(&self) -> impl Iterator, &DataValue)> + '_ { let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest); self.data .iter() .map(move |(id, val)| (tm.data_enum_id(id.0), val)) } // ── typed field accessors ───────────────────────────────────────────────── pub fn get_bool(&self, data_type: DataType) -> Option { self.get_data_opt(data_type)?.as_bool() } pub fn get_str(&self, data_type: DataType) -> Option<&str> { self.get_data_opt(data_type)?.as_str() } pub fn get_u128(&self, data_type: DataType) -> Option { self.get_data_opt(data_type)?.as_unsigned_number() } pub fn get_i128(&self, data_type: DataType) -> Option { self.get_data_opt(data_type)?.as_signed_number() } pub fn get_float(&self, data_type: DataType) -> Option<(u8, u32)> { self.get_data_opt(data_type)?.as_float() } pub fn get_bytes(&self, data_type: DataType) -> Option<&[u8]> { self.get_data_opt(data_type)?.as_bytes_slice() } pub fn get_array(&self, data_type: DataType) -> Option<&[DataValue]> { self.get_data_opt(data_type)?.as_array_slice() } } impl CommunicationValue { /* * Frame format (strict new format): * [4 bytes u32 total_length] // number of bytes after this field * [2 bytes u16 communication_type] * [1 byte flags] * [optional 4 bytes id] // if flags bit2 set * [optional 6 bytes sender] // if flags bit0 set * [optional 6 bytes receiver] // if flags bit1 set * [optional 1 byte signature type] // if flags bit4 set; Type defines length of signature * [optional signature] // if flags bit4 set * [data container bytes...] * * Flags: * bit0 => has sender * bit1 => has receiver * bit2 => has id * bit3 => is data encrypted If so data bytes will be an encrypted container * bit4 => is communication value signed */ /* * Build the canonical metadata header and data payload shared by both * `to_bytes` and `build_signed_payload`. Keeping a single source here * guarantees the serialized frame and the signed-over bytes stay in sync. * * Returns `(metadata, data_bytes)` where * metadata = comm_type || flags || id? || sender? || receiver? * * `force_signed` forces the `FLAG_SIGNED` bit on regardless of whether a * signature is currently attached. The signed-payload path passes `true` so * that the bytes signed by `sign_frame` (before the signature is stored) and * the bytes verified by `verify_frame` (after it is stored) are identical. */ fn build_metadata_and_data( &self, force_signed: bool, ) -> Result<(Vec, Vec), CodecError> { let has_sender = self.sender != 0; let has_receiver = self.receiver != 0; let has_id = self.id != 0; #[cfg(feature = "crypto")] let is_encrypted = self.data.len() == 1 && self.data.values().any(|v| { matches!( v, DataValue::EncryptedContainer(_) | DataValue::SignedEncryptedContainer(_) ) }); #[cfg(not(feature = "crypto"))] let is_encrypted = false; #[cfg(feature = "crypto")] let has_frame_sig = self.frame_signature.is_some(); #[cfg(not(feature = "crypto"))] let has_frame_sig = false; let mut flags: u8 = 0; if has_sender { flags |= FLAG_HAS_SENDER; } if has_receiver { flags |= FLAG_HAS_RECEIVER; } if has_id { flags |= FLAG_HAS_ID; } if is_encrypted { flags |= FLAG_ENCRYPTED; } if has_frame_sig || force_signed { flags |= FLAG_SIGNED; } let mut metadata = Vec::new(); let _ = metadata.write_u16::(self.comm_type.0); metadata.push(flags); if has_id { let _ = metadata.write_u32::(self.id); } if has_sender { let sender_be = self.sender.to_be_bytes(); metadata.extend_from_slice(&sender_be[2..]); } if has_receiver { let receiver_be = self.receiver.to_be_bytes(); metadata.extend_from_slice(&receiver_be[2..]); } #[cfg(feature = "crypto")] let data_bytes = if is_encrypted { self.data .values() .find_map(|v| match v { DataValue::EncryptedContainer(ct) => Some(ct.clone()), DataValue::SignedEncryptedContainer(ct) => Some(ct.clone()), _ => None, }) .unwrap_or_default() } else { DataValue::container_from_map(&self.data).to_bytes()? }; #[cfg(not(feature = "crypto"))] let data_bytes = DataValue::container_from_map(&self.data).to_bytes()?; Ok((metadata, data_bytes)) } pub fn to_bytes(&self) -> Result, CodecError> { let (metadata, data_bytes) = self.build_metadata_and_data(false)?; let mut payload = Vec::new(); payload.extend_from_slice(&metadata); #[cfg(feature = "crypto")] if let Some((alg, sig)) = &self.frame_signature { // algorithm and signature are computed by sign_frame() and stored. // The frame bytes are built by using the pre-computed signature. payload.push(*alg); payload.extend_from_slice(sig); } payload.extend_from_slice(&data_bytes); let len = u32::try_from(payload.len()).map_err(|_| CodecError::TooManyEntries)?; let mut frame = Vec::with_capacity(4 + payload.len()); frame .write_u32::(len) .map_err(|_| CodecError::InvalidEncoding)?; frame.extend_from_slice(&payload); Ok(frame) } pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = Cursor::new(bytes); let total_len = cursor .read_u32::() .map_err(|_| CodecError::InvalidEncoding)? as usize; if bytes.len() < 4 + total_len { return Err(CodecError::InvalidEncoding); } let frame_end = 4 + total_len; let comm_type_num = cursor .read_u16::() .map_err(|_| CodecError::InvalidEncoding)?; let comm_type = CommunicationTypeId(comm_type_num); let flags = cursor.read_u8().map_err(|_| CodecError::InvalidEncoding)?; let has_sender = (flags & FLAG_HAS_SENDER) != 0; let has_receiver = (flags & FLAG_HAS_RECEIVER) != 0; let has_id = (flags & FLAG_HAS_ID) != 0; let is_encrypted = (flags & FLAG_ENCRYPTED) != 0; let is_signed = (flags & FLAG_SIGNED) != 0; #[cfg(not(feature = "crypto"))] if is_signed || is_encrypted { return Err(CodecError::InvalidEncoding); } let id = if has_id { cursor .read_u32::() .map_err(|_| CodecError::InvalidEncoding)? } else { 0 }; let sender = if has_sender { let mut buf = [0u8; 8]; cursor .read_exact(&mut buf[2..]) .map_err(|_| CodecError::InvalidEncoding)?; u64::from_be_bytes(buf) } else { 0 }; let receiver = if has_receiver { let mut buf = [0u8; 8]; cursor .read_exact(&mut buf[2..]) .map_err(|_| CodecError::InvalidEncoding)?; u64::from_be_bytes(buf) } else { 0 }; #[cfg(feature = "crypto")] let frame_signature = if is_signed { let alg = cursor.read_u8().map_err(|_| CodecError::InvalidEncoding)?; let sig_len = SigAlgorithm::length(alg).ok_or(CodecError::InvalidEncoding)?; let mut sig = vec![0u8; sig_len]; cursor .read_exact(&mut sig) .map_err(|_| CodecError::InvalidEncoding)?; Some((alg, sig)) } else { None }; let pos = cursor.position() as usize; if pos > frame_end { return Err(CodecError::InvalidEncoding); } let data_bytes = &bytes[pos..frame_end]; #[cfg(feature = "crypto")] let data = if is_encrypted { let mut map = BTreeMap::new(); map.insert( DataType::Version.to_id(&TypeMap::latest()), DataValue::EncryptedContainer(data_bytes.to_vec()), ); map } else { let data_value = DataValue::from_bytes(data_bytes).ok_or(CodecError::InvalidEncoding)?; data_value.as_map().ok_or(CodecError::InvalidEncoding)? }; #[cfg(not(feature = "crypto"))] let data = { let data_value = DataValue::from_bytes(data_bytes).ok_or(CodecError::InvalidEncoding)?; data_value.as_map().ok_or(CodecError::InvalidEncoding)? }; Ok(Self { id, comm_type, sender, receiver, data, type_map: None, #[cfg(feature = "crypto")] frame_signature, }) } pub fn from_bytes_with(bytes: &[u8], tm: &TypeMap) -> Result { let mut val = Self::from_bytes(bytes)?; val.type_map = Some(tm.clone()); Ok(val) } /* * Sign the frame. Computes a signature over the canonical form: * comm_type || flags || id? || sender? || receiver? || data_bytes * * After calling this, `to_bytes()` will embed the algorithm and * signature before the data payload. */ #[cfg(feature = "crypto")] pub fn sign_frame(&mut self, algorithm: u8, signer: &impl SignatureScheme) -> Option<()> { let signed_payload = self.build_signed_payload().ok()?; let sig = signer.sign(&signed_payload).ok()?; self.frame_signature = Some((algorithm, sig)); Some(()) } /* * Verify the frame signature. Reconstructs the signed payload from * current state and checks it against the stored signature. */ #[cfg(feature = "crypto")] pub fn verify_frame(&self, verifier: &impl SignatureScheme) -> Result<(), CodecError> { let (_algorithm, sig) = self .frame_signature .as_ref() .ok_or(CodecError::InvalidEncoding)?; let signed_payload = self.build_signed_payload()?; verifier .verify(&signed_payload, sig) .map_err(|_| CodecError::InvalidEncoding) } /* * Reconstruct the signed payload that the frame signature covers: * comm_type || flags || id? || sender? || receiver? || data_bytes */ #[cfg(feature = "crypto")] fn build_signed_payload(&self) -> Result, CodecError> { // Force FLAG_SIGNED on so the signed bytes match whether or not the // signature has been attached yet (sign_frame runs before storing it). let (metadata, data_bytes) = self.build_metadata_and_data(true)?; Ok([metadata, data_bytes].concat()) } #[cfg(feature = "crypto")] pub fn get_frame_signature(&self) -> Option<&(u8, Vec)> { self.frame_signature.as_ref() } /* * Verify the frame signature using a `PublicKeyBundle`. Dispatches to * Ed25519, ML-DSA-65, or both (DUAL) based on the stored algorithm byte. * Returns `false` if the frame has no signature or verification fails. */ #[cfg(feature = "crypto")] pub fn validate_signature(&self, pk: &PublicKeyBundle) -> bool { let Some((alg, _)) = &self.frame_signature else { return false; }; struct Ed25519Verifier<'a>(&'a mtp_crypto::SignaturePublicKey); impl SignatureScheme for Ed25519Verifier<'_> { fn sign(&self, _: &[u8]) -> Result, mtp_crypto::CryptoError> { Err(mtp_crypto::CryptoError::SigningFailed) } fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), mtp_crypto::CryptoError> { mtp_crypto::verify_ed25519(self.0, msg, sig) } } struct MlDsaVerifier<'a>(&'a mtp_crypto::SignaturePqPublicKey); impl SignatureScheme for MlDsaVerifier<'_> { fn sign(&self, _: &[u8]) -> Result, mtp_crypto::CryptoError> { Err(mtp_crypto::CryptoError::SigningFailed) } fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), mtp_crypto::CryptoError> { mtp_crypto::verify_ml_dsa(self.0, msg, sig) } } match *alg { SigAlgorithm::ED25519 => self .verify_frame(&Ed25519Verifier(&pk.sig_cl_public_key)) .is_ok(), SigAlgorithm::ML_DSA_65 => self .verify_frame(&MlDsaVerifier(&pk.sig_pq_public_key)) .is_ok(), SigAlgorithm::DUAL => { // For DUAL, verify_frame passes the full combined sig to the verifier. // We wrap a verifier that splits and checks both halves. struct DualVerifier<'a>( &'a mtp_crypto::SignaturePublicKey, &'a mtp_crypto::SignaturePqPublicKey, ); impl SignatureScheme for DualVerifier<'_> { fn sign(&self, _: &[u8]) -> Result, mtp_crypto::CryptoError> { Err(mtp_crypto::CryptoError::SigningFailed) } fn verify( &self, msg: &[u8], sig: &[u8], ) -> Result<(), mtp_crypto::CryptoError> { const ED_LEN: usize = 64; if sig.len() < ED_LEN { return Err(mtp_crypto::CryptoError::InvalidSignature); } mtp_crypto::verify_ed25519(self.0, msg, &sig[..ED_LEN])?; mtp_crypto::verify_ml_dsa(self.1, msg, &sig[ED_LEN..]) } } self.verify_frame(&DualVerifier(&pk.sig_cl_public_key, &pk.sig_pq_public_key)) .is_ok() } _ => false, } } #[cfg(feature = "registry")] pub fn migrate(&self, target_tm: &TypeMap) -> Result { let comm_name = communication_type_name(self.comm_type.0) .ok_or_else(|| CodecError::UnknownCommunicationType(self.comm_type.0.to_string()))?; let comm_variant = CommunicationType::from_name(comm_name) .ok_or_else(|| CodecError::UnknownCommunicationType(comm_name.to_string()))?; let new_comm_id = CommunicationTypeId( target_tm .comm_id_enum(comm_variant) .ok_or_else(|| CodecError::UnknownCommunicationType(comm_name.to_string()))?, ); let mut new_data = BTreeMap::new(); for (&old_id, value) in &self.data { let name = data_type_name(old_id.0) .ok_or_else(|| CodecError::UnknownDataType(old_id.0.to_string()))?; let variant = DataType::from_name(name) .ok_or_else(|| CodecError::UnknownDataType(name.to_string()))?; let new_id = DataTypeId( target_tm .data_id_enum(variant) .ok_or_else(|| CodecError::UnknownDataType(name.to_string()))?, ); new_data.insert(new_id, value.clone()); } Ok(Self { id: self.id, comm_type: new_comm_id, sender: self.sender, receiver: self.receiver, data: new_data, type_map: Some(target_tm.clone()), #[cfg(feature = "crypto")] frame_signature: self.frame_signature.clone(), }) } } fn fmt_data_value(val: &DataValue, f: &mut fmt::Formatter<'_>) -> fmt::Result { match val { DataValue::Container(entries) => { write!(f, "{{")?; for (i, (key, value)) in entries.iter().enumerate() { if i > 0 { write!(f, ", ")?; } let name = data_type_name(key.0).unwrap_or("?"); write!(f, "{}: ", name)?; fmt_data_value(value, f)?; } write!(f, "}}") } DataValue::Array(arr) => { write!(f, "[")?; for (i, value) in arr.iter().enumerate() { if i > 0 { write!(f, ", ")?; } fmt_data_value(value, f)?; } write!(f, "]") } #[cfg(feature = "crypto")] DataValue::EncryptedContainer(_) => write!(f, "(Secure)"), DataValue::Bytes(_) => write!(f, "(Binary)"), other => write!(f, "{}", other), } } #[cfg(debug_assertions)] const BOLD_BLUE: &str = "\x1b[1;34m"; #[cfg(not(debug_assertions))] const BOLD_BLUE: &str = ""; #[cfg(debug_assertions)] const GREEN: &str = "\x1b[32m"; #[cfg(not(debug_assertions))] const GREEN: &str = ""; #[cfg(debug_assertions)] const YELLOW: &str = "\x1b[33m"; #[cfg(not(debug_assertions))] const YELLOW: &str = ""; #[cfg(debug_assertions)] const ORANGE: &str = "\x1b[38;5;208m"; #[cfg(not(debug_assertions))] const ORANGE: &str = ""; #[cfg(debug_assertions)] const RESET: &str = "\x1b[0m"; #[cfg(not(debug_assertions))] const RESET: &str = ""; impl fmt::Display for CommunicationValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let version = self .type_map .as_ref() .map(|tm| &tm.version) .unwrap_or(&PROTOCOL_VERSION); write!(f, "V{}{}{}", BOLD_BLUE, version, RESET)?; if self.id != 0 { write!(f, ", ID:{}{:X}{}", GREEN, self.id, RESET)?; } if self.sender != 0 { write!(f, ", S:{}{:X}{}", YELLOW, self.sender, RESET)?; } if self.receiver != 0 { write!(f, ", R:{}{:X}{}", ORANGE, self.receiver, RESET)?; } let name = self .get_comm_type_enum() .map(|t| t.name()) .unwrap_or_else(|| communication_type_name(self.comm_type.0).unwrap_or("?")); write!(f, ", {}: ", name)?; let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest); write!(f, "{{")?; for (i, (raw_id, value)) in self.data.iter().enumerate() { if i > 0 { write!(f, ", ")?; } let dname = tm .data_enum_id(raw_id.0) .map(|t| t.name()) .or_else(|| data_type_name(raw_id.0)) .unwrap_or("?"); write!(f, "{}: ", dname)?; fmt_data_value(value, f)?; } write!(f, "}}") } } /* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; use crate::data_value::DataValue; fn roundtrip(cv: CommunicationValue) -> Result> { let bytes = cv.to_bytes()?; let decoded = CommunicationValue::from_bytes(&bytes)?; let bytes2 = decoded.to_bytes()?; assert_eq!(bytes, bytes2); Ok(decoded) } #[test] fn test_flags_and_order_without_optional() -> Result<(), Box> { let cv = CommunicationValue::new(CommunicationType::ErrorParsing).with_id(0); let bytes = cv.to_bytes()?; // [u32 len][u16 type][flags]... assert!(bytes.len() >= 7); let mut c = Cursor::new(bytes.as_slice()); let total_len = c.read_u32::()?; assert_eq!(total_len as usize + 4, bytes.len()); let typ = c.read_u16::()?; assert_eq!(typ, 12); let flags = c.read_u8()?; assert_eq!(flags & 0b0000_0111, 0); Ok(()) } #[test] fn test_flags_and_order_with_all_optional() -> Result<(), Box> { let cv = CommunicationValue::new(CommunicationType::ErrorBadVersion) .with_id(0xAABBCCDD) .with_sender(0x0000_1122_3344_5566) .with_receiver(0x0000_6677_8899_AABB); let bytes = cv.to_bytes()?; let mut c = Cursor::new(bytes.as_slice()); let total_len = c.read_u32::()?; assert_eq!(total_len as usize + 4, bytes.len()); let typ = c.read_u16::()?; assert_eq!(typ, 13); let flags = c.read_u8()?; assert_eq!(flags & 0b0000_0111, 0b0000_0111); let id = c.read_u32::()?; assert_eq!(id, 0xAABBCCDD); let mut sender6 = [0u8; 6]; c.read_exact(&mut sender6)?; assert_eq!(sender6, [0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut receiver6 = [0u8; 6]; c.read_exact(&mut receiver6)?; assert_eq!(receiver6, [0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB]); Ok(()) } #[test] fn test_roundtrip_complex() -> Result<(), Box> { let tm = TypeMap::latest(); let cv = CommunicationValue::new(CommunicationType::Disconnect) .with_id(1234) .with_sender(111) .with_receiver(222) .add_typed_default(DataType::Id, DataValue::Str("alice".to_string())) .add_typed_default(DataType::ClientNonce, DataValue::SignedNumber(42)) .add_typed_default(DataType::ServerNonce, DataValue::BoolTrue) .add_typed_default( DataType::PublicKeys, DataValue::Array(vec![DataValue::SignedNumber(1), DataValue::SignedNumber(2)]), ); let decoded = roundtrip(cv.clone())?; assert_eq!(decoded.get_id(), 1234); assert_eq!(decoded.get_sender(), 111); assert_eq!(decoded.get_receiver(), 222); assert_eq!(decoded.get_type(), CommunicationType::Disconnect.to_id(&tm)); assert_eq!( decoded.get_data(DataType::Id), &DataValue::Str("alice".to_string()) ); assert_eq!( decoded.get_data(DataType::ClientNonce), &DataValue::SignedNumber(42) ); Ok(()) } #[test] fn test_corrupted_length_returns_none() { let mut bad = vec![0u8; 8]; // total_length claims more than available bad[0..4].copy_from_slice(&(1000u32.to_be_bytes())); assert!(CommunicationValue::from_bytes(&bad).is_err()); } #[cfg(feature = "crypto")] #[test] fn test_sign_verify_frame_roundtrip() -> Result<(), Box> { use mtp_crypto::{Ed25519Signer, SigAlgorithm}; let (signer, sk, _pk) = Ed25519Signer::generate(); let mut cv = CommunicationValue::new(CommunicationType::Ping) .with_id(7) .with_sender(1) .with_receiver(2) .add_typed_default(DataType::PqSignature, DataValue::UnsignedNumber(42)); assert!(cv.sign_frame(SigAlgorithm::ED25519, &signer).is_some()); // Same in-memory value verifies (FLAG_SIGNED forced on both sides). let verifier = Ed25519Signer::new(&sk)?; assert!(cv.verify_frame(&verifier).is_ok()); // Survives a wire round-trip. let bytes = cv.to_bytes()?; let decoded = CommunicationValue::from_bytes(&bytes)?; assert!(decoded.verify_frame(&verifier).is_ok()); Ok(()) } #[cfg(feature = "crypto")] #[test] fn test_verify_frame_wrong_key_fails() -> Result<(), Box> { use mtp_crypto::{Ed25519Signer, SigAlgorithm}; let (signer, _, _) = Ed25519Signer::generate(); let (_, other_sk, _) = Ed25519Signer::generate(); let mut cv = CommunicationValue::new(CommunicationType::Ping) .add_typed_default(DataType::PqSignature, DataValue::UnsignedNumber(42)); assert!(cv.sign_frame(SigAlgorithm::ED25519, &signer).is_some()); let wrong = Ed25519Signer::new(&other_sk)?; assert!(cv.verify_frame(&wrong).is_err()); Ok(()) } }