Crypto
WASM
TESTS
This commit is contained in:
Alex Emmet 2026-06-25 22:08:44 +02:00
commit 687e6f9642
49 changed files with 6272 additions and 366 deletions

View file

@ -324,8 +324,7 @@ impl CommunicationValue {
#[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 sig_len = SigAlgorithm::length(alg).ok_or(CodecError::InvalidEncoding)?;
let mut sig = vec![0u8; sig_len];
cursor
.read_exact(&mut sig)
@ -389,11 +388,7 @@ impl CommunicationValue {
* signature before the data payload.
*/
#[cfg(feature = "crypto")]
pub fn sign_frame(
&mut self,
algorithm: u8,
signer: &impl SignatureScheme,
) -> Option<()> {
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));

View file

@ -151,10 +151,17 @@ impl DataValue {
const KIND_NULL: u8 = 0xFF;
/// Smallest possible encoded entry, used to cap pre-reservation when
/// decoding containers/arrays so a small frame cannot force a huge
/// allocation from an attacker-controlled count. A bool/null entry in a
/// container is 3 bytes (1 kind + 2 key); a bare value in an array is 1
/// byte, so 1 is the safe lower bound shared by both.
const MIN_ENTRY_BYTES: usize = 1;
pub fn container_from_map(map: &BTreeMap<DataTypeId, DataValue>) -> DataValue {
let mut container = Vec::new();
for (key, value) in map {
container.push((key.clone(), value.clone()));
container.push((*key, value.clone()));
}
DataValue::Container(container)
}
@ -407,7 +414,7 @@ impl DataValue {
DataValue::Container(c) => {
let mut out = BTreeMap::new();
for (k, v) in c {
out.insert(k.clone(), v.clone());
out.insert(*k, v.clone());
}
Some(out)
}
@ -452,7 +459,7 @@ impl DataValue {
.map_err(|_| CodecError::InvalidEncoding)?;
for (key, value) in entries {
Self::write_container_entry(&mut out, key.clone(), value)?;
Self::write_container_entry(&mut out, *key, value)?;
}
Ok(out)
}
@ -520,14 +527,11 @@ impl DataValue {
match value {
DataValue::BoolTrue => Ok(()),
DataValue::BoolFalse => Ok(()),
#[allow(clippy::if_same_then_else)]
DataValue::Bool(v) => {
// Kept intentionally: the kind marker already encodes the boolean,
// so both arms carry no payload. Retained for clear compatibility.
if *v {
Ok(())
} else {
Ok(())
}
if *v { Ok(()) } else { Ok(()) }
}
DataValue::SignedNumber(n) => {
buf.write_i128::<BigEndian>(*n)
@ -607,7 +611,11 @@ impl DataValue {
fn try_read_container(cursor: &mut Cursor<&[u8]>) -> Option<Self> {
let count = cursor.read_u16::<BigEndian>().ok()? as usize;
let mut entries = Vec::with_capacity(count);
let remaining = cursor
.get_ref()
.len()
.saturating_sub(cursor.position() as usize);
let mut entries = Vec::with_capacity(count.min(remaining / Self::MIN_ENTRY_BYTES));
for _ in 0..count {
let kind = cursor.read_u8().ok()?;
@ -653,7 +661,11 @@ impl DataValue {
fn read_array(cursor: &mut Cursor<&[u8]>) -> Option<Self> {
let count = cursor.read_u16::<BigEndian>().ok()? as usize;
let mut out = Vec::with_capacity(count);
let remaining = cursor
.get_ref()
.len()
.saturating_sub(cursor.position() as usize);
let mut out = Vec::with_capacity(count.min(remaining / Self::MIN_ENTRY_BYTES));
for _ in 0..count {
let kind = cursor.read_u8().ok()?;
@ -1185,6 +1197,22 @@ mod tests {
assert!(DataValue::from_bytes(&bytes[..0]).is_none());
}
#[test]
fn test_oversized_count_does_not_overallocate() {
// A frame declaring 65535 entries but carrying almost no payload must be
// rejected without pre-reserving a Vec for 65535 entries. The capacity is
// 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());
// 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());
}
#[test]
fn test_display_basic() {
assert_eq!(format!("{}", DataValue::BoolTrue), "true");

View file

@ -6,8 +6,8 @@ pub use data_value::{DataKind, DataValue};
pub use mtp_common::CodecError;
pub use mtp_type_map::{
communication_type_name, data_type_name, CommunicationType, CommunicationTypeId, DataType,
DataTypeId, TypeMap, Version, PROTOCOL_VERSION,
CommunicationType, CommunicationTypeId, DataType, DataTypeId, PROTOCOL_VERSION, TypeMap,
Version, communication_type_name, data_type_name,
};
pub(crate) fn rand_u32() -> u32 {