General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 4m20s
Some checks failed
CI / checks (push) Failing after 4m20s
This commit is contained in:
parent
5f11d476b6
commit
02be09ef26
122 changed files with 10309 additions and 5206 deletions
2
codec/Cargo.lock
generated
2
codec/Cargo.lock
generated
|
|
@ -60,7 +60,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|||
|
||||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
[package]
|
||||
name = "mtp-codec"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mtp-type-map = { version = "0.1.0", path = "../type-map" }
|
||||
mtp-common = { version = "0.1.0", path = "../common" }
|
||||
mtp-crypto = { version = "0.1.0", path = "../crypto", optional = true }
|
||||
mtp-type-map = { version = "0.2.0", path = "../type-map" }
|
||||
mtp-common = { version = "0.2.0", path = "../common" }
|
||||
mtp-crypto = { version = "0.2.0", path = "../crypto", optional = true }
|
||||
base64 = "0.22"
|
||||
byteorder = "1.5"
|
||||
rand = { version = "0.8", features = ["std", "std_rng"] }
|
||||
rand = { version = "0.10.1", features = ["std", "std_rng"] }
|
||||
|
||||
[features]
|
||||
registry = ["mtp-type-map/registry"]
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@ use std::io::{Cursor, Read};
|
|||
use crate::data_value::{DataKind, DataValue};
|
||||
use crate::rand_u32;
|
||||
use mtp_common::CodecError;
|
||||
#[cfg(all(test, feature = "registry"))]
|
||||
use mtp_type_map::Version;
|
||||
use mtp_type_map::{
|
||||
CommunicationType, CommunicationTypeId, DataType, DataTypeId, PROTOCOL_VERSION, TypeMap,
|
||||
communication_type_name, data_type_name,
|
||||
};
|
||||
|
||||
/// Largest sender or receiver identifier representable by the six-byte wire fields.
|
||||
pub const MAX_WIRE_ID: u64 = (1 << 48) - 1;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
use mtp_crypto::{PublicKeyBundle, SigAlgorithm, SignatureScheme};
|
||||
|
||||
|
|
@ -19,6 +23,28 @@ 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;
|
||||
const FLAG_SIGNED_ENCRYPTED: u8 = 0b0010_0000;
|
||||
|
||||
/// An opaque, frame-level encrypted payload.
|
||||
///
|
||||
/// This is separate from [`DataValue`] because encrypted frame bytes are not a
|
||||
/// typed data map until they have been decrypted.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg(feature = "crypto")]
|
||||
pub enum EncryptedPayload {
|
||||
Plain(Vec<u8>),
|
||||
Signed(Vec<u8>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
impl EncryptedPayload {
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Plain(bytes) | Self::Signed(bytes) => bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommunicationValue {
|
||||
|
|
@ -27,7 +53,10 @@ pub struct CommunicationValue {
|
|||
sender: u64,
|
||||
receiver: u64,
|
||||
data: BTreeMap<DataTypeId, DataValue>,
|
||||
#[cfg(feature = "crypto")]
|
||||
encrypted_payload: Option<EncryptedPayload>,
|
||||
type_map: Option<TypeMap>,
|
||||
mapping_error: Option<CodecError>,
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature: Option<(u8, Vec<u8>)>,
|
||||
}
|
||||
|
|
@ -36,14 +65,19 @@ impl CommunicationValue {
|
|||
#[must_use]
|
||||
pub fn new(comm_type: CommunicationType) -> Self {
|
||||
let tm = TypeMap::new(PROTOCOL_VERSION);
|
||||
let id = comm_type.to_id(&tm);
|
||||
let id = comm_type.try_to_id(&tm);
|
||||
Self {
|
||||
id: rand_u32(),
|
||||
comm_type: id,
|
||||
comm_type: id.unwrap_or(CommunicationTypeId(0)),
|
||||
sender: 0,
|
||||
receiver: 0,
|
||||
data: BTreeMap::new(),
|
||||
#[cfg(feature = "crypto")]
|
||||
encrypted_payload: None,
|
||||
type_map: Some(tm),
|
||||
mapping_error: id
|
||||
.is_none()
|
||||
.then(|| CodecError::UnknownCommunicationType(comm_type.name().to_string())),
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature: None,
|
||||
}
|
||||
|
|
@ -52,14 +86,19 @@ impl CommunicationValue {
|
|||
#[cfg(feature = "registry")]
|
||||
#[must_use]
|
||||
pub fn from_comm(comm_type: CommunicationType, tm: &TypeMap) -> Self {
|
||||
let id = comm_type.to_id(tm);
|
||||
let id = comm_type.try_to_id(tm);
|
||||
Self {
|
||||
id: rand_u32(),
|
||||
comm_type: id,
|
||||
comm_type: id.unwrap_or(CommunicationTypeId(0)),
|
||||
sender: 0,
|
||||
receiver: 0,
|
||||
data: BTreeMap::new(),
|
||||
#[cfg(feature = "crypto")]
|
||||
encrypted_payload: None,
|
||||
type_map: Some(tm.clone()),
|
||||
mapping_error: id
|
||||
.is_none()
|
||||
.then(|| CodecError::UnknownCommunicationType(comm_type.name().to_string())),
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature: None,
|
||||
}
|
||||
|
|
@ -99,8 +138,22 @@ impl CommunicationValue {
|
|||
self.comm_type
|
||||
}
|
||||
|
||||
/// Returns the protocol type map attached to this frame.
|
||||
pub fn type_map(&self) -> Option<&TypeMap> {
|
||||
self.type_map.as_ref()
|
||||
}
|
||||
|
||||
/// Binds the frame's numeric type identifiers to a protocol version.
|
||||
pub fn set_type_map(&mut self, tm: &TypeMap) {
|
||||
self.type_map = Some(tm.clone());
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn add_data(mut self, data: DataTypeId, value: DataValue) -> Self {
|
||||
#[cfg(feature = "crypto")]
|
||||
{
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
self.data.insert(data, value);
|
||||
self
|
||||
}
|
||||
|
|
@ -108,14 +161,30 @@ impl CommunicationValue {
|
|||
#[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);
|
||||
#[cfg(feature = "crypto")]
|
||||
{
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
if let Some(id) = data.try_to_id(tm) {
|
||||
self.data.insert(id, value);
|
||||
} else if self.mapping_error.is_none() {
|
||||
self.mapping_error = Some(CodecError::UnknownDataType(data.name().to_string()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn add_typed_default(mut self, data: DataType, value: DataValue) -> Self {
|
||||
#[cfg(feature = "crypto")]
|
||||
{
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest);
|
||||
self.data.insert(data.to_id(&tm), value);
|
||||
if let Some(id) = data.try_to_id(&tm) {
|
||||
self.data.insert(id, value);
|
||||
} else if self.mapping_error.is_none() {
|
||||
self.mapping_error = Some(CodecError::UnknownDataType(data.name().to_string()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -170,10 +239,54 @@ impl CommunicationValue {
|
|||
&self.data
|
||||
}
|
||||
|
||||
/// Returns the number of cleartext data entries.
|
||||
///
|
||||
/// An encrypted frame has no cleartext entries until
|
||||
/// [`Self::set_decrypted_container`] is called.
|
||||
pub fn data_len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[must_use]
|
||||
pub fn with_encrypted_payload(mut self, payload: EncryptedPayload) -> Self {
|
||||
self.data.clear();
|
||||
self.encrypted_payload = Some(payload);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[must_use]
|
||||
pub fn encrypted_payload(&self) -> Option<&EncryptedPayload> {
|
||||
self.encrypted_payload.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[must_use]
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
self.encrypted_payload.is_some()
|
||||
}
|
||||
|
||||
/// Replaces an opaque encrypted payload with its decrypted typed entries.
|
||||
#[cfg(feature = "crypto")]
|
||||
pub fn set_decrypted_container(
|
||||
&mut self,
|
||||
entries: impl IntoIterator<Item = (DataTypeId, DataValue)>,
|
||||
) {
|
||||
self.data = entries.into_iter().collect();
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
|
||||
/// Returns the number of logical payload items available in the frame.
|
||||
#[must_use]
|
||||
pub fn payload_len(&self) -> usize {
|
||||
#[cfg(feature = "crypto")]
|
||||
if self.encrypted_payload.is_some() {
|
||||
return 1;
|
||||
}
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
// ── type checks ──────────────────────────────────────────────────────────
|
||||
|
||||
pub fn is_type(&self, comm_type: CommunicationType) -> bool {
|
||||
|
|
@ -181,15 +294,23 @@ impl CommunicationValue {
|
|||
}
|
||||
|
||||
pub fn get_type_name(&self) -> Option<&'static str> {
|
||||
communication_type_name(self.comm_type.0)
|
||||
self.type_map
|
||||
.as_ref()
|
||||
.and_then(|tm| tm.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);
|
||||
if let Some(id) = data_type.try_to_id(&tm) {
|
||||
#[cfg(feature = "crypto")]
|
||||
{
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
self.data.insert(id, value);
|
||||
} else if self.mapping_error.is_none() {
|
||||
self.mapping_error = Some(CodecError::UnknownDataType(data_type.name().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +341,13 @@ impl CommunicationValue {
|
|||
}
|
||||
|
||||
pub fn merge(&mut self, other: &CommunicationValue) {
|
||||
if self.mapping_error.is_none() {
|
||||
self.mapping_error.clone_from(&other.mapping_error);
|
||||
}
|
||||
#[cfg(feature = "crypto")]
|
||||
if !other.data.is_empty() {
|
||||
self.encrypted_payload = None;
|
||||
}
|
||||
for (id, value) in &other.data {
|
||||
self.data.insert(*id, value.clone());
|
||||
}
|
||||
|
|
@ -252,7 +380,7 @@ impl CommunicationValue {
|
|||
self.get_data_opt(data_type)?.as_signed_number()
|
||||
}
|
||||
|
||||
pub fn get_float(&self, data_type: DataType) -> Option<(u8, u32)> {
|
||||
pub fn get_float(&self, data_type: DataType) -> Option<f64> {
|
||||
self.get_data_opt(data_type)?.as_float()
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +412,7 @@ impl CommunicationValue {
|
|||
* bit2 => has id
|
||||
* bit3 => is data encrypted If so data bytes will be an encrypted container
|
||||
* bit4 => is communication value signed
|
||||
* bit5 => encrypted payload contains a signed container
|
||||
*/
|
||||
/*
|
||||
* Build the canonical metadata header and data payload shared by both
|
||||
|
|
@ -302,21 +431,25 @@ impl CommunicationValue {
|
|||
&self,
|
||||
force_signed: bool,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), CodecError> {
|
||||
if let Some(error) = &self.mapping_error {
|
||||
return Err(error.clone());
|
||||
}
|
||||
if self.sender > MAX_WIRE_ID || self.receiver > MAX_WIRE_ID {
|
||||
return Err(CodecError::InvalidEncoding);
|
||||
}
|
||||
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(_)
|
||||
)
|
||||
});
|
||||
let is_encrypted = self.encrypted_payload.is_some();
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
let is_encrypted = false;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
let is_signed_encrypted =
|
||||
matches!(self.encrypted_payload, Some(EncryptedPayload::Signed(_)));
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
let has_frame_sig = self.frame_signature.is_some();
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
|
|
@ -335,6 +468,10 @@ impl CommunicationValue {
|
|||
if is_encrypted {
|
||||
flags |= FLAG_ENCRYPTED;
|
||||
}
|
||||
#[cfg(feature = "crypto")]
|
||||
if is_signed_encrypted {
|
||||
flags |= FLAG_SIGNED_ENCRYPTED;
|
||||
}
|
||||
if has_frame_sig || force_signed {
|
||||
flags |= FLAG_SIGNED;
|
||||
}
|
||||
|
|
@ -358,17 +495,9 @@ impl CommunicationValue {
|
|||
}
|
||||
|
||||
#[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()?
|
||||
let data_bytes = match &self.encrypted_payload {
|
||||
Some(payload) => payload.as_bytes().to_vec(),
|
||||
None => DataValue::container_from_map(&self.data).to_bytes()?,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
|
|
@ -409,12 +538,13 @@ impl CommunicationValue {
|
|||
let total_len = cursor
|
||||
.read_u32::<BigEndian>()
|
||||
.map_err(|_| CodecError::InvalidEncoding)? as usize;
|
||||
if bytes.len() < 4 + total_len {
|
||||
let frame_end = 4usize
|
||||
.checked_add(total_len)
|
||||
.ok_or(CodecError::InvalidEncoding)?;
|
||||
if bytes.len() != frame_end {
|
||||
return Err(CodecError::InvalidEncoding);
|
||||
}
|
||||
|
||||
let frame_end = 4 + total_len;
|
||||
|
||||
let comm_type_num = cursor
|
||||
.read_u16::<BigEndian>()
|
||||
.map_err(|_| CodecError::InvalidEncoding)?;
|
||||
|
|
@ -426,9 +556,14 @@ impl CommunicationValue {
|
|||
let has_id = (flags & FLAG_HAS_ID) != 0;
|
||||
let is_encrypted = (flags & FLAG_ENCRYPTED) != 0;
|
||||
let is_signed = (flags & FLAG_SIGNED) != 0;
|
||||
let is_signed_encrypted = (flags & FLAG_SIGNED_ENCRYPTED) != 0;
|
||||
|
||||
if is_signed_encrypted && !is_encrypted {
|
||||
return Err(CodecError::InvalidEncoding);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
if is_signed || is_encrypted {
|
||||
if is_signed || is_encrypted || is_signed_encrypted {
|
||||
return Err(CodecError::InvalidEncoding);
|
||||
}
|
||||
|
||||
|
|
@ -481,17 +616,20 @@ impl CommunicationValue {
|
|||
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
|
||||
let (encrypted_payload, data) = if is_encrypted {
|
||||
let payload = if is_signed_encrypted {
|
||||
EncryptedPayload::Signed(data_bytes.to_vec())
|
||||
} else {
|
||||
EncryptedPayload::Plain(data_bytes.to_vec())
|
||||
};
|
||||
(Some(payload), BTreeMap::new())
|
||||
} else {
|
||||
let data_value =
|
||||
DataValue::from_bytes(data_bytes).ok_or(CodecError::InvalidEncoding)?;
|
||||
data_value.as_map().ok_or(CodecError::InvalidEncoding)?
|
||||
(
|
||||
None,
|
||||
data_value.as_map().ok_or(CodecError::InvalidEncoding)?,
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
|
|
@ -507,7 +645,10 @@ impl CommunicationValue {
|
|||
sender,
|
||||
receiver,
|
||||
data,
|
||||
type_map: None,
|
||||
#[cfg(feature = "crypto")]
|
||||
encrypted_payload,
|
||||
type_map: Some(TypeMap::new(PROTOCOL_VERSION)),
|
||||
mapping_error: None,
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature,
|
||||
})
|
||||
|
|
@ -515,7 +656,7 @@ impl CommunicationValue {
|
|||
|
||||
pub fn from_bytes_with(bytes: &[u8], tm: &TypeMap) -> Result<Self, CodecError> {
|
||||
let mut val = Self::from_bytes(bytes)?;
|
||||
val.type_map = Some(tm.clone());
|
||||
val.set_type_map(tm);
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
|
|
@ -635,8 +776,18 @@ impl CommunicationValue {
|
|||
}
|
||||
|
||||
#[cfg(feature = "registry")]
|
||||
/// Migrates this frame to `target_tm`.
|
||||
///
|
||||
/// Migration changes the signed wire representation, so any existing frame
|
||||
/// signature is discarded. Call [`Self::sign_frame`] after migration when
|
||||
/// the migrated frame needs to be authenticated.
|
||||
pub fn migrate(&self, target_tm: &TypeMap) -> Result<Self, CodecError> {
|
||||
let comm_name = communication_type_name(self.comm_type.0)
|
||||
if let Some(error) = &self.mapping_error {
|
||||
return Err(error.clone());
|
||||
}
|
||||
let source_tm = self.type_map.as_ref().ok_or(CodecError::InvalidEncoding)?;
|
||||
let comm_name = source_tm
|
||||
.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()))?;
|
||||
|
|
@ -648,7 +799,8 @@ impl CommunicationValue {
|
|||
|
||||
let mut new_data = BTreeMap::new();
|
||||
for (&old_id, value) in &self.data {
|
||||
let name = data_type_name(old_id.0)
|
||||
let name = source_tm
|
||||
.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()))?;
|
||||
|
|
@ -666,14 +818,17 @@ impl CommunicationValue {
|
|||
sender: self.sender,
|
||||
receiver: self.receiver,
|
||||
data: new_data,
|
||||
type_map: Some(target_tm.clone()),
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature: self.frame_signature.clone(),
|
||||
encrypted_payload: self.encrypted_payload.clone(),
|
||||
type_map: Some(target_tm.clone()),
|
||||
mapping_error: None,
|
||||
#[cfg(feature = "crypto")]
|
||||
frame_signature: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_data_value(val: &DataValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn fmt_data_value(val: &DataValue, tm: &TypeMap, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match val {
|
||||
DataValue::Container(entries) => {
|
||||
write!(f, "{{")?;
|
||||
|
|
@ -681,9 +836,9 @@ fn fmt_data_value(val: &DataValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
let name = data_type_name(key.0).unwrap_or("?");
|
||||
let name = tm.data_type_name(key.0).unwrap_or("?");
|
||||
write!(f, "{}: ", name)?;
|
||||
fmt_data_value(value, f)?;
|
||||
fmt_data_value(value, tm, f)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
|
|
@ -693,7 +848,7 @@ fn fmt_data_value(val: &DataValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
fmt_data_value(value, f)?;
|
||||
fmt_data_value(value, tm, f)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
|
|
@ -745,25 +900,27 @@ impl fmt::Display for CommunicationValue {
|
|||
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("?"));
|
||||
let name = self.get_comm_type_enum().map(|t| t.name()).unwrap_or("?");
|
||||
write!(f, ", {}: ", name)?;
|
||||
|
||||
let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest);
|
||||
write!(f, "{{")?;
|
||||
#[cfg(feature = "crypto")]
|
||||
if let Some(payload) = &self.encrypted_payload {
|
||||
match payload {
|
||||
EncryptedPayload::Plain(bytes) => write!(f, "(Encrypted, {} bytes)", bytes.len())?,
|
||||
EncryptedPayload::Signed(bytes) => {
|
||||
write!(f, "(SignedEncrypted, {} bytes)", bytes.len())?
|
||||
}
|
||||
}
|
||||
}
|
||||
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("?");
|
||||
let dname = tm.data_enum_id(raw_id.0).map(|t| t.name()).unwrap_or("?");
|
||||
write!(f, "{}: ", dname)?;
|
||||
fmt_data_value(value, f)?;
|
||||
fmt_data_value(value, &tm, f)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
|
|
@ -854,7 +1011,12 @@ mod tests {
|
|||
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_type(),
|
||||
CommunicationType::Disconnect
|
||||
.try_to_id(&tm)
|
||||
.expect("built-in type must be mapped")
|
||||
);
|
||||
assert_eq!(
|
||||
decoded.get_data(DataType::Id),
|
||||
&DataValue::Str("alice".to_string())
|
||||
|
|
@ -866,6 +1028,190 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_ids_are_limited_to_wire_width() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let max = CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_sender(MAX_WIRE_ID)
|
||||
.with_receiver(MAX_WIRE_ID);
|
||||
let decoded = roundtrip(max)?;
|
||||
assert_eq!(decoded.get_sender(), MAX_WIRE_ID);
|
||||
assert_eq!(decoded.get_receiver(), MAX_WIRE_ID);
|
||||
|
||||
assert!(
|
||||
CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_sender(MAX_WIRE_ID + 1)
|
||||
.to_bytes()
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_receiver(MAX_WIRE_ID + 1)
|
||||
.to_bytes()
|
||||
.is_err()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "registry")]
|
||||
#[test]
|
||||
fn missing_version_mappings_return_codec_errors() {
|
||||
let v0 = TypeMap::new(Version(0, 0));
|
||||
assert_eq!(DataType::AnotherType.try_to_id(&v0), None);
|
||||
|
||||
let data_error = CommunicationValue::from_comm(CommunicationType::Ping, &v0)
|
||||
.add_typed(
|
||||
DataType::AnotherType,
|
||||
&v0,
|
||||
DataValue::Str("not available in v0".into()),
|
||||
)
|
||||
.to_bytes();
|
||||
assert_eq!(
|
||||
data_error,
|
||||
Err(CodecError::UnknownDataType("AnotherType".into()))
|
||||
);
|
||||
|
||||
let unknown_version = TypeMap::new(Version(99, 0));
|
||||
assert_eq!(CommunicationType::Ping.try_to_id(&unknown_version), None);
|
||||
assert_eq!(
|
||||
CommunicationValue::from_comm(CommunicationType::Ping, &unknown_version).to_bytes(),
|
||||
Err(CodecError::UnknownCommunicationType("Ping".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "registry")]
|
||||
#[test]
|
||||
fn decoded_and_migrated_frames_use_the_source_version_map()
|
||||
-> Result<(), Box<dyn std::error::Error>> {
|
||||
let v1 = TypeMap::new(Version(1, 0));
|
||||
let v2 = TypeMap::new(Version(2, 0));
|
||||
let original = CommunicationValue::from_comm(CommunicationType::Ping, &v1).add_typed(
|
||||
DataType::SomeType,
|
||||
&v1,
|
||||
DataValue::Str("v1 value".into()),
|
||||
);
|
||||
let bytes = original.to_bytes()?;
|
||||
|
||||
let decoded = CommunicationValue::from_bytes_with(&bytes, &v1)?;
|
||||
assert_eq!(decoded.get_type_name(), Some("Ping"));
|
||||
assert_eq!(
|
||||
decoded.get_data(DataType::SomeType),
|
||||
&DataValue::Str("v1 value".into())
|
||||
);
|
||||
assert_eq!(
|
||||
decoded.type_map().map(|tm| &tm.version),
|
||||
Some(&Version(1, 0))
|
||||
);
|
||||
|
||||
let migrated = decoded.migrate(&v2)?;
|
||||
assert_eq!(
|
||||
migrated.get_data(DataType::SomeType).as_str(),
|
||||
Some("v1 value")
|
||||
);
|
||||
assert_eq!(
|
||||
migrated.data().get(
|
||||
&DataType::SomeType
|
||||
.try_to_id(&v2)
|
||||
.expect("SomeType must be mapped in v2"),
|
||||
),
|
||||
Some(&DataValue::Str("v1 value".into()))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[test]
|
||||
fn test_plain_encrypted_payload_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ciphertext = vec![1, 2, 3, 4, 5];
|
||||
let cv = CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_encrypted_payload(EncryptedPayload::Plain(ciphertext.clone()));
|
||||
|
||||
let bytes = cv.to_bytes()?;
|
||||
assert_ne!(bytes[6] & FLAG_ENCRYPTED, 0);
|
||||
assert_eq!(bytes[6] & FLAG_SIGNED_ENCRYPTED, 0);
|
||||
|
||||
let decoded = roundtrip(cv)?;
|
||||
assert_eq!(
|
||||
decoded.encrypted_payload(),
|
||||
Some(&EncryptedPayload::Plain(ciphertext))
|
||||
);
|
||||
assert!(decoded.data().is_empty());
|
||||
assert_eq!(decoded.data_len(), 0);
|
||||
assert_eq!(decoded.payload_len(), 1);
|
||||
assert_eq!(decoded.get_data(DataType::Version), &DataValue::Null);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[test]
|
||||
fn test_signed_encrypted_payload_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ciphertext = vec![9, 8, 7, 6];
|
||||
let cv = CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_encrypted_payload(EncryptedPayload::Signed(ciphertext.clone()));
|
||||
|
||||
let bytes = cv.to_bytes()?;
|
||||
assert_ne!(bytes[6] & FLAG_ENCRYPTED, 0);
|
||||
assert_ne!(bytes[6] & FLAG_SIGNED_ENCRYPTED, 0);
|
||||
|
||||
let decoded = roundtrip(cv)?;
|
||||
assert_eq!(
|
||||
decoded.encrypted_payload(),
|
||||
Some(&EncryptedPayload::Signed(ciphertext))
|
||||
);
|
||||
assert!(decoded.data().is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[test]
|
||||
fn test_decrypted_payload_serializes_as_cleartext() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
let data_id = DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("built-in type must be mapped");
|
||||
let mut cv = CommunicationValue::new(CommunicationType::Ping)
|
||||
.with_encrypted_payload(EncryptedPayload::Plain(vec![1, 2, 3]));
|
||||
|
||||
cv.set_decrypted_container([(data_id, DataValue::Str("clear".into()))]);
|
||||
|
||||
assert!(!cv.is_encrypted());
|
||||
assert_eq!(cv.payload_len(), 1);
|
||||
assert_eq!(cv.get_data(DataType::Version).as_str(), Some("clear"));
|
||||
let bytes = cv.to_bytes()?;
|
||||
assert_eq!(bytes[6] & FLAG_ENCRYPTED, 0);
|
||||
assert_eq!(bytes[6] & FLAG_SIGNED_ENCRYPTED, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[test]
|
||||
fn test_nested_encrypted_value_remains_typed_data() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cv = CommunicationValue::new(CommunicationType::Ping).add_typed_default(
|
||||
DataType::Version,
|
||||
DataValue::SignedEncryptedContainer(vec![4, 3, 2, 1]),
|
||||
);
|
||||
|
||||
let bytes = cv.to_bytes()?;
|
||||
assert_eq!(bytes[6] & FLAG_ENCRYPTED, 0);
|
||||
assert_eq!(bytes[6] & FLAG_SIGNED_ENCRYPTED, 0);
|
||||
|
||||
let decoded = roundtrip(cv)?;
|
||||
assert!(!decoded.is_encrypted());
|
||||
assert!(matches!(
|
||||
decoded.get_data(DataType::Version),
|
||||
DataValue::SignedEncryptedContainer(bytes) if bytes == &[4, 3, 2, 1]
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signed_encrypted_flag_requires_encrypted_flag() {
|
||||
let mut bytes = CommunicationValue::new(CommunicationType::Ping)
|
||||
.to_bytes()
|
||||
.expect("frame should encode");
|
||||
bytes[6] |= FLAG_SIGNED_ENCRYPTED;
|
||||
assert!(CommunicationValue::from_bytes(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_corrupted_length_returns_none() {
|
||||
let mut bad = vec![0u8; 8];
|
||||
|
|
@ -874,6 +1220,16 @@ mod tests {
|
|||
assert!(CommunicationValue::from_bytes(&bad).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_bytes_are_rejected() {
|
||||
let mut bytes = CommunicationValue::new(CommunicationType::Ping)
|
||||
.to_bytes()
|
||||
.expect("frame should encode");
|
||||
bytes.extend_from_slice(&[0xAA, 0xBB]);
|
||||
|
||||
assert!(CommunicationValue::from_bytes(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
#[test]
|
||||
fn test_sign_verify_frame_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
|
@ -918,4 +1274,22 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "crypto", feature = "registry"))]
|
||||
#[test]
|
||||
fn test_migrate_discards_frame_signature() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use mtp_crypto::{Ed25519Signer, SigAlgorithm};
|
||||
use mtp_type_map::Version;
|
||||
|
||||
let (signer, _, _) = Ed25519Signer::generate();
|
||||
let mut cv = CommunicationValue::new(CommunicationType::Ping)
|
||||
.add_typed_default(DataType::Version, DataValue::Str("1.0".into()));
|
||||
assert!(cv.sign_frame(SigAlgorithm::ED25519, &signer).is_some());
|
||||
assert!(cv.get_frame_signature().is_some());
|
||||
|
||||
let migrated = cv.migrate(&TypeMap::new(Version(2, 0)))?;
|
||||
|
||||
assert!(migrated.get_frame_signature().is_none());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ impl fmt::Display for DataKind {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DataValue {
|
||||
BoolTrue,
|
||||
BoolFalse,
|
||||
|
|
@ -69,7 +69,8 @@ pub enum DataValue {
|
|||
|
||||
SignedNumber(i128),
|
||||
UnsignedNumber(u128),
|
||||
Float(u8, u32),
|
||||
/// An IEEE-754 double-precision floating-point value.
|
||||
Float(f64),
|
||||
|
||||
Str(String),
|
||||
Bytes(Vec<u8>),
|
||||
|
|
@ -120,6 +121,10 @@ pub enum DataValue {
|
|||
|
||||
impl DataValue {
|
||||
/*
|
||||
* Top-level format:
|
||||
* [1 byte kind]
|
||||
* [remaining bytes payload] // interpreted according to kind
|
||||
*
|
||||
* Container format:
|
||||
* [2 bytes u16 entry_count] // number of entries
|
||||
* [1 byte kind] // DataValue kind marker
|
||||
|
|
@ -198,7 +203,7 @@ impl DataValue {
|
|||
DataValue::Bool(_) | DataValue::BoolTrue | DataValue::BoolFalse => DataKind::Bool,
|
||||
DataValue::SignedNumber(_) => DataKind::SignedNumber,
|
||||
DataValue::UnsignedNumber(_) => DataKind::UnsignedNumber,
|
||||
DataValue::Float(_, _) => DataKind::Float,
|
||||
DataValue::Float(_) => DataKind::Float,
|
||||
DataValue::Str(_) => DataKind::Str,
|
||||
DataValue::Array(a) => {
|
||||
if let Some(first) = a.first() {
|
||||
|
|
@ -253,9 +258,9 @@ impl DataValue {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> Option<(u8, u32)> {
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
DataValue::Float(a, b) => Some((*a, *b)),
|
||||
DataValue::Float(value) => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -337,7 +342,7 @@ impl DataValue {
|
|||
DataValue::Bool(_) | DataValue::BoolTrue | DataValue::BoolFalse => "Bool",
|
||||
DataValue::SignedNumber(_) => "SignedNumber",
|
||||
DataValue::UnsignedNumber(_) => "UnsignedNumber",
|
||||
DataValue::Float(_, _) => "Float",
|
||||
DataValue::Float(_) => "Float",
|
||||
DataValue::Str(_) => "Str",
|
||||
DataValue::Bytes(_) => "Bytes",
|
||||
DataValue::Array(_) => "Array",
|
||||
|
|
@ -426,7 +431,7 @@ impl DataValue {
|
|||
#[cfg(feature = "crypto")]
|
||||
pub fn sign_container(&mut self, algorithm: u8, signer: &impl SignatureScheme) -> Option<()> {
|
||||
let entries = self.as_container()?;
|
||||
let container_bytes = Self::encode_container(&entries).ok()?;
|
||||
let container_bytes = DataValue::Container(entries).to_bytes().ok()?;
|
||||
|
||||
let sig = signer.sign(&container_bytes).ok()?;
|
||||
|
||||
|
|
@ -576,20 +581,17 @@ impl DataValue {
|
|||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, CodecError> {
|
||||
match self {
|
||||
DataValue::Container(entries) => Self::encode_container(entries),
|
||||
DataValue::Array(arr) => Self::encode_array(arr),
|
||||
_ => {
|
||||
let mut out = Vec::new();
|
||||
Self::write_value_payload(&mut out, self)?;
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
out.push(Self::kind_marker(self));
|
||||
Self::write_value_payload(&mut out, self)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let value = Self::read_value(&mut cursor, true)?;
|
||||
let kind = cursor.read_u8().ok()?;
|
||||
let payload_len = bytes.len().checked_sub(1)?;
|
||||
let value = Self::read_value_by_kind(&mut cursor, kind, Some(payload_len))?;
|
||||
if cursor.position() as usize != bytes.len() {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -694,9 +696,8 @@ impl DataValue {
|
|||
.map_err(|_| CodecError::InvalidEncoding)?;
|
||||
Ok(())
|
||||
}
|
||||
DataValue::Float(a, b) => {
|
||||
buf.write_u8(*a).map_err(|_| CodecError::InvalidEncoding)?;
|
||||
buf.write_u32::<BigEndian>(*b)
|
||||
DataValue::Float(value) => {
|
||||
buf.write_f64::<BigEndian>(*value)
|
||||
.map_err(|_| CodecError::InvalidEncoding)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -738,28 +739,6 @@ impl DataValue {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_value(cursor: &mut Cursor<&[u8]>, top_level: bool) -> Option<Self> {
|
||||
if top_level {
|
||||
let start = cursor.position() as usize;
|
||||
let remaining = cursor.get_ref().len().checked_sub(start)?;
|
||||
if remaining < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let snapshot = cursor.clone();
|
||||
if let Some(container) = Self::try_read_container(cursor) {
|
||||
return Some(container);
|
||||
}
|
||||
*cursor = snapshot;
|
||||
|
||||
let array = Self::read_array(cursor)?;
|
||||
return Some(array);
|
||||
}
|
||||
|
||||
let kind = cursor.read_u8().ok()?;
|
||||
Self::read_value_by_kind(cursor, kind, None)
|
||||
}
|
||||
|
||||
fn try_read_container(cursor: &mut Cursor<&[u8]>) -> Option<Self> {
|
||||
let count = cursor.read_u16::<BigEndian>().ok()? as usize;
|
||||
let remaining = cursor
|
||||
|
|
@ -837,11 +816,7 @@ impl DataValue {
|
|||
Self::KIND_UNSIGNED_NUMBER => Some(DataValue::UnsignedNumber(
|
||||
cursor.read_u128::<BigEndian>().ok()?,
|
||||
)),
|
||||
Self::KIND_FLOAT => {
|
||||
let a = cursor.read_u8().ok()?;
|
||||
let b = cursor.read_u32::<BigEndian>().ok()?;
|
||||
Some(DataValue::Float(a, b))
|
||||
}
|
||||
Self::KIND_FLOAT => Some(DataValue::Float(cursor.read_f64::<BigEndian>().ok()?)),
|
||||
Self::KIND_STR => {
|
||||
let s = std::str::from_utf8(Self::read_payload_slice(cursor, payload_len?)?)
|
||||
.ok()?
|
||||
|
|
@ -884,7 +859,7 @@ impl DataValue {
|
|||
)),
|
||||
Self::KIND_NULL => Some(DataValue::Null),
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
0x0A | 0x0B | 0x0C => None,
|
||||
0x0A..=0x0C => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -902,7 +877,7 @@ impl DataValue {
|
|||
}
|
||||
DataValue::SignedNumber(_) => Self::KIND_SIGNED_NUMBER,
|
||||
DataValue::UnsignedNumber(_) => Self::KIND_UNSIGNED_NUMBER,
|
||||
DataValue::Float(_, _) => Self::KIND_FLOAT,
|
||||
DataValue::Float(_) => Self::KIND_FLOAT,
|
||||
DataValue::Str(_) => Self::KIND_STR,
|
||||
DataValue::Array(_) => Self::KIND_ARRAY,
|
||||
DataValue::Bytes(_) => Self::KIND_BYTES,
|
||||
|
|
@ -953,7 +928,7 @@ impl fmt::Display for DataValue {
|
|||
DataValue::Bool(v) => write!(f, "{}", v),
|
||||
DataValue::SignedNumber(n) => write!(f, "{}", n),
|
||||
DataValue::UnsignedNumber(n) => write!(f, "{}", n),
|
||||
DataValue::Float(exp, mant) => write!(f, "{}e{}", mant, exp),
|
||||
DataValue::Float(value) => write!(f, "{}", value),
|
||||
DataValue::Str(s) => write!(f, "\"{}\"", s),
|
||||
DataValue::Container(entries) => {
|
||||
write!(f, "{{")?;
|
||||
|
|
@ -997,7 +972,7 @@ impl PartialEq for DataValue {
|
|||
(Bool(a), Bool(b)) => a == b,
|
||||
(SignedNumber(a), SignedNumber(b)) => a == b,
|
||||
(UnsignedNumber(a), UnsignedNumber(b)) => a == b,
|
||||
(Float(a, b), Float(c, d)) => a == c && b == d,
|
||||
(Float(a), Float(b)) => a.to_bits() == b.to_bits(),
|
||||
(Str(a), Str(b)) => a == b,
|
||||
(Array(a), Array(b)) => a == b,
|
||||
(Bytes(a), Bytes(b)) => a == b,
|
||||
|
|
@ -1014,6 +989,8 @@ impl PartialEq for DataValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for DataValue {}
|
||||
|
||||
impl Hash for DataValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
use DataValue::*;
|
||||
|
|
@ -1025,10 +1002,7 @@ impl Hash for DataValue {
|
|||
BoolTrue | BoolFalse | Bool(_) | Null => {}
|
||||
SignedNumber(n) => n.hash(state),
|
||||
UnsignedNumber(n) => n.hash(state),
|
||||
Float(n, m) => {
|
||||
n.hash(state);
|
||||
m.hash(state);
|
||||
}
|
||||
Float(value) => value.to_bits().hash(state),
|
||||
Str(s) => s.hash(state),
|
||||
Array(a) => a.hash(state),
|
||||
Bytes(a) => a.hash(state),
|
||||
|
|
@ -1200,7 +1174,9 @@ impl TryFrom<DataValue> for Vec<u8> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn container_roundtrip(values: Vec<(DataTypeId, DataValue)>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn container_roundtrip(
|
||||
values: Vec<(DataTypeId, DataValue)>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let dv = DataValue::Container(values.clone());
|
||||
let bytes = dv.to_bytes()?;
|
||||
let decoded = DataValue::from_bytes(&bytes).ok_or("roundtrip failed")?;
|
||||
|
|
@ -1216,12 +1192,97 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn value_roundtrip(value: DataValue) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bytes = value.to_bytes()?;
|
||||
assert_eq!(bytes.first(), Some(&DataValue::kind_marker(&value)));
|
||||
let decoded = DataValue::from_bytes(&bytes).ok_or("roundtrip failed")?;
|
||||
assert_eq!(value, decoded, "value roundtrip mismatch");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_every_top_level_variant_roundtrips() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let values = vec![
|
||||
DataValue::BoolTrue,
|
||||
DataValue::BoolFalse,
|
||||
DataValue::Bool(true),
|
||||
DataValue::Bool(false),
|
||||
DataValue::SignedNumber(i128::MIN),
|
||||
DataValue::UnsignedNumber(u128::MAX),
|
||||
DataValue::Float(-0.125),
|
||||
DataValue::Str("top level".to_string()),
|
||||
DataValue::Bytes(vec![0x00, 0xFF, 0x42]),
|
||||
DataValue::Array(vec![DataValue::Str("nested".to_string())]),
|
||||
DataValue::Container(vec![(DataTypeId(7), DataValue::BoolTrue)]),
|
||||
DataValue::Null,
|
||||
];
|
||||
|
||||
for value in values {
|
||||
value_roundtrip(value)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
for value in [
|
||||
DataValue::EncryptedContainer(vec![1, 2, 3]),
|
||||
DataValue::SignedContainer(vec![4, 5, 6]),
|
||||
DataValue::SignedEncryptedContainer(vec![7, 8, 9]),
|
||||
] {
|
||||
value_roundtrip(value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_container_and_array_have_distinct_framing() {
|
||||
let container = DataValue::Container(vec![])
|
||||
.to_bytes()
|
||||
.expect("container should encode");
|
||||
let array = DataValue::Array(vec![])
|
||||
.to_bytes()
|
||||
.expect("array should encode");
|
||||
|
||||
assert_ne!(container, array);
|
||||
assert_eq!(
|
||||
DataValue::from_bytes(&container),
|
||||
Some(DataValue::Container(vec![]))
|
||||
);
|
||||
assert_eq!(
|
||||
DataValue::from_bytes(&array),
|
||||
Some(DataValue::Array(vec![]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_level_trailing_bytes_are_rejected() {
|
||||
let mut boolean = DataValue::BoolTrue
|
||||
.to_bytes()
|
||||
.expect("boolean should encode");
|
||||
boolean.push(0x00);
|
||||
assert!(DataValue::from_bytes(&boolean).is_none());
|
||||
|
||||
let mut number = DataValue::SignedNumber(42)
|
||||
.to_bytes()
|
||||
.expect("number should encode");
|
||||
number.push(0x00);
|
||||
assert!(DataValue::from_bytes(&number).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Id.to_id(&tm), DataValue::BoolTrue),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::BoolFalse),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::BoolTrue,
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::BoolFalse,
|
||||
),
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1245,18 +1306,34 @@ mod tests {
|
|||
fn test_signed_number_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::SignedNumber(0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(42)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(0),
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(42),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(-42),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataType::ServerNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(i128::MAX),
|
||||
),
|
||||
(
|
||||
DataType::PublicKeys.to_id(&tm),
|
||||
DataType::PublicKeys
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(i128::MIN),
|
||||
),
|
||||
])?;
|
||||
|
|
@ -1267,10 +1344,22 @@ mod tests {
|
|||
fn test_unsigned_number_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::UnsignedNumber(0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(0),
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(42),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(u128::MAX),
|
||||
),
|
||||
])?;
|
||||
|
|
@ -1281,11 +1370,23 @@ mod tests {
|
|||
fn test_float_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Float(0, 0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::Float(2, 12345)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Float(255, 4294967295),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Float(0.0),
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Float(1_234_500.0),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Float(f64::MAX),
|
||||
),
|
||||
])?;
|
||||
Ok(())
|
||||
|
|
@ -1295,10 +1396,22 @@ mod tests {
|
|||
fn test_str_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Str(String::new())),
|
||||
(DataType::Id.to_id(&tm), DataValue::Str("hello".to_string())),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str(String::new()),
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("hello".to_string()),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("a".repeat(1000)),
|
||||
),
|
||||
])?;
|
||||
|
|
@ -1309,13 +1422,22 @@ mod tests {
|
|||
fn test_bytes_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Bytes(vec![])),
|
||||
(
|
||||
DataType::Id.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Bytes(vec![]),
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Bytes(vec![0x00, 0xFF, 0xAB]),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Bytes(vec![0x42; 100]),
|
||||
),
|
||||
])?;
|
||||
|
|
@ -1325,7 +1447,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_null_in_container() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![(DataType::Version.to_id(&tm), DataValue::Null)])?;
|
||||
container_roundtrip(vec![(
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Null,
|
||||
)])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1359,17 +1486,36 @@ mod tests {
|
|||
fn test_container_mixed_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::BoolTrue),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(-100)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::BoolTrue,
|
||||
),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(-100),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("test".to_string()),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataType::ServerNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(u128::MAX),
|
||||
),
|
||||
(DataType::PublicKeys.to_id(&tm), DataValue::Null),
|
||||
(
|
||||
DataType::PublicKeys
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Null,
|
||||
),
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1379,11 +1525,20 @@ mod tests {
|
|||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Container(vec![(DataType::Error.to_id(&tm), DataValue::BoolTrue)]),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Container(vec![(
|
||||
DataType::Error
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::BoolTrue,
|
||||
)]),
|
||||
),
|
||||
(
|
||||
DataType::Id.to_id(&tm),
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Array(vec![DataValue::SignedNumber(1), DataValue::SignedNumber(2)]),
|
||||
),
|
||||
])?;
|
||||
|
|
@ -1394,7 +1549,9 @@ mod tests {
|
|||
fn test_container_base64_roundtrip() {
|
||||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![(
|
||||
DataType::Description.to_id(&tm),
|
||||
DataType::Description
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]),
|
||||
)]);
|
||||
let b64 = dv.to_base64().expect("encode failed");
|
||||
|
|
@ -1411,7 +1568,7 @@ mod tests {
|
|||
DataValue::UnsignedNumber(0).kind(),
|
||||
DataKind::UnsignedNumber
|
||||
);
|
||||
assert_eq!(DataValue::Float(0, 0).kind(), DataKind::Float);
|
||||
assert_eq!(DataValue::Float(0.0).kind(), DataKind::Float);
|
||||
assert_eq!(DataValue::Str(String::new()).kind(), DataKind::Str);
|
||||
assert_eq!(DataValue::Bytes(vec![]).kind(), DataKind::Bytes);
|
||||
assert_eq!(
|
||||
|
|
@ -1427,39 +1584,66 @@ mod tests {
|
|||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("alice".to_string()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(42)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(42),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Bytes(vec![0x01, 0x02]),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataType::ServerNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Array(vec![DataValue::BoolTrue]),
|
||||
),
|
||||
]);
|
||||
|
||||
let map = dv.as_map().expect("should be a container");
|
||||
assert_eq!(
|
||||
map.get(&DataType::Version.to_id(&tm))
|
||||
.and_then(|v| v.as_str()),
|
||||
map.get(
|
||||
&DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped")
|
||||
)
|
||||
.and_then(|v| v.as_str()),
|
||||
Some("alice")
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::Id.to_id(&tm))
|
||||
.and_then(|v| v.as_signed_number()),
|
||||
map.get(
|
||||
&DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped")
|
||||
)
|
||||
.and_then(|v| v.as_signed_number()),
|
||||
Some(42)
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::ClientNonce.to_id(&tm))
|
||||
.and_then(|v| v.as_bytes()),
|
||||
map.get(
|
||||
&DataType::ClientNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped")
|
||||
)
|
||||
.and_then(|v| v.as_bytes()),
|
||||
Some(vec![0x01, 0x02])
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::ServerNonce.to_id(&tm))
|
||||
.and_then(|v| v.as_array()),
|
||||
map.get(
|
||||
&DataType::ServerNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped")
|
||||
)
|
||||
.and_then(|v| v.as_array()),
|
||||
Some(vec![DataValue::BoolTrue])
|
||||
);
|
||||
}
|
||||
|
|
@ -1474,7 +1658,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_as_float() {
|
||||
assert_eq!(DataValue::Float(3, 14).as_float(), Some((3, 14)));
|
||||
assert_eq!(DataValue::Float(-0.125).as_float(), Some(-0.125));
|
||||
assert_eq!(DataValue::Null.as_float(), None);
|
||||
}
|
||||
|
||||
|
|
@ -1482,8 +1666,18 @@ mod tests {
|
|||
fn test_container_from_map() {
|
||||
let tm = TypeMap::latest();
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(DataType::Version.to_id(&tm), DataValue::BoolTrue);
|
||||
map.insert(DataType::Id.to_id(&tm), DataValue::SignedNumber(99));
|
||||
map.insert(
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::BoolTrue,
|
||||
);
|
||||
map.insert(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::SignedNumber(99),
|
||||
);
|
||||
let dv = DataValue::container_from_map(&map);
|
||||
let container = dv.as_container().expect("should be container");
|
||||
assert_eq!(container.len(), 2);
|
||||
|
|
@ -1492,7 +1686,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_invalid_short_input() {
|
||||
assert!(DataValue::from_bytes(&[]).is_none());
|
||||
assert!(DataValue::from_bytes(&[0x01]).is_none());
|
||||
assert!(DataValue::from_bytes(&[DataValue::KIND_SIGNED_NUMBER]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1505,7 +1699,9 @@ mod tests {
|
|||
fn test_truncated_container_rejected() {
|
||||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("hello".to_string()),
|
||||
)]);
|
||||
let bytes = dv.to_bytes().expect("encode failed");
|
||||
|
|
@ -1521,13 +1717,12 @@ mod tests {
|
|||
// capped against remaining bytes, so these decode attempts allocate at
|
||||
// most a handful of slots before failing.
|
||||
// Container path: count = 0xFFFF, no entries follow.
|
||||
assert!(DataValue::from_bytes(&[0xFF, 0xFF]).is_none());
|
||||
assert!(DataValue::from_bytes(&[DataValue::KIND_CONTAINER, 0xFF, 0xFF]).is_none());
|
||||
// Container path with one stray byte after the count.
|
||||
assert!(DataValue::from_bytes(&[0xFF, 0xFF, 0x01]).is_none());
|
||||
// Array path: force the container parse to fail first, then the array
|
||||
// parse also sees the oversized count. A leading kind byte that is not a
|
||||
// valid container entry makes try_read_container bail to the array path.
|
||||
assert!(DataValue::from_bytes(&[0xFF, 0xFF, 0x08, 0xFF, 0xFF]).is_none());
|
||||
assert!(DataValue::from_bytes(&[DataValue::KIND_CONTAINER, 0xFF, 0xFF, 0x01]).is_none());
|
||||
// Array path: the tagged array also declares 65535 entries but carries
|
||||
// only a single entry byte.
|
||||
assert!(DataValue::from_bytes(&[DataValue::KIND_ARRAY, 0xFF, 0xFF, 0x01]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1557,8 +1752,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_float_display() {
|
||||
let s = format!("{}", DataValue::Float(2, 12345));
|
||||
assert_eq!(s, "12345e2");
|
||||
let s = format!("{}", DataValue::Float(1.25));
|
||||
assert_eq!(s, "1.25");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1566,11 +1761,15 @@ mod tests {
|
|||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataType::ServerNonce
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("v2.0".to_string()),
|
||||
),
|
||||
(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
DataType::PqSignature
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(42),
|
||||
),
|
||||
]);
|
||||
|
|
@ -1614,7 +1813,10 @@ mod tests {
|
|||
assert_eq!(i64::try_from(DataValue::SignedNumber(10))?, 10i64);
|
||||
assert_eq!(u128::try_from(DataValue::UnsignedNumber(99))?, 99u128);
|
||||
assert_eq!(u64::try_from(DataValue::UnsignedNumber(7))?, 7u64);
|
||||
assert_eq!(Vec::<u8>::try_from(DataValue::Bytes(vec![0xAB]))?, vec![0xABu8]);
|
||||
assert_eq!(
|
||||
Vec::<u8>::try_from(DataValue::Bytes(vec![0xAB]))?,
|
||||
vec![0xABu8]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1646,10 +1848,17 @@ mod tests {
|
|||
|
||||
let mut dv = DataValue::Container(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("secret".to_string()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
(
|
||||
DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::UnsignedNumber(42),
|
||||
),
|
||||
]);
|
||||
|
||||
assert!(
|
||||
|
|
@ -1675,7 +1884,9 @@ mod tests {
|
|||
let keyring_b = Keyring::generate();
|
||||
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("secret".to_string()),
|
||||
)]);
|
||||
|
||||
|
|
@ -1698,7 +1909,9 @@ mod tests {
|
|||
let keyring = Keyring::generate();
|
||||
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("secret".to_string()),
|
||||
)]);
|
||||
|
||||
|
|
@ -1740,7 +1953,9 @@ mod tests {
|
|||
let (signer, sk, _pk) = Ed25519Signer::generate();
|
||||
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("signed data".to_string()),
|
||||
)]);
|
||||
|
||||
|
|
@ -1782,7 +1997,9 @@ mod tests {
|
|||
let wrong_verifier = Ed25519Signer::new(&sk2)?;
|
||||
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.expect("test type must be mapped"),
|
||||
DataValue::Str("signed data".to_string()),
|
||||
)]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
pub mod communication_value;
|
||||
pub mod data_value;
|
||||
|
||||
pub use communication_value::CommunicationValue;
|
||||
#[cfg(feature = "crypto")]
|
||||
pub use communication_value::EncryptedPayload;
|
||||
pub use communication_value::{CommunicationValue, MAX_WIRE_ID};
|
||||
pub use data_value::{DataKind, DataValue};
|
||||
pub use mtp_common::CodecError;
|
||||
|
||||
pub use mtp_type_map::{
|
||||
CommunicationType, CommunicationTypeId, DataType, DataTypeId, PROTOCOL_VERSION, TypeMap,
|
||||
Version, communication_type_name, data_type_name,
|
||||
Version,
|
||||
};
|
||||
|
||||
pub(crate) fn rand_u32() -> u32 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue