use alloc::vec;
use alloc::vec::Vec;
use super::types::{AdvFlags, ResourceError};
use crate::constants::{RESOURCE_HASHMAP_MAX_LEN, RESOURCE_MAPHASH_LEN};
use crate::msgpack::{self, Value};
#[derive(Debug, Clone)]
pub struct ResourceAdvertisement {
pub transfer_size: u64,
pub data_size: u64,
pub num_parts: u64,
pub resource_hash: Vec<u8>,
pub random_hash: Vec<u8>,
pub original_hash: Vec<u8>,
pub hashmap: Vec<u8>,
pub flags: AdvFlags,
pub segment_index: u64,
pub total_segments: u64,
pub request_id: Option<Vec<u8>>,
}
impl ResourceAdvertisement {
pub fn pack(&self, segment: usize) -> Vec<u8> {
let hashmap_start = segment * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
let max_end = (segment + 1) * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
let hashmap_end = core::cmp::min(max_end, self.hashmap.len());
let hashmap_segment = if hashmap_start < self.hashmap.len() {
&self.hashmap[hashmap_start..hashmap_end]
} else {
&[]
};
let q_value = match &self.request_id {
Some(id) => Value::Bin(id.clone()),
None => Value::Nil,
};
let entries: Vec<(&str, Value)> = vec![
("t", Value::UInt(self.transfer_size)),
("d", Value::UInt(self.data_size)),
("n", Value::UInt(self.num_parts)),
("h", Value::Bin(self.resource_hash.clone())),
("r", Value::Bin(self.random_hash.clone())),
("o", Value::Bin(self.original_hash.clone())),
("i", Value::UInt(self.segment_index)),
("l", Value::UInt(self.total_segments)),
("q", q_value),
("f", Value::UInt(self.flags.to_byte() as u64)),
("m", Value::Bin(hashmap_segment.to_vec())),
];
msgpack::pack_str_map(&entries)
}
pub fn unpack(data: &[u8]) -> Result<Self, ResourceError> {
let value = msgpack::unpack_exact(data).map_err(|_| ResourceError::InvalidAdvertisement)?;
let t = value
.map_get("t")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)?;
let d = value
.map_get("d")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)?;
let n = value
.map_get("n")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)?;
let h = value
.map_get("h")
.and_then(|v| v.as_bin())
.ok_or(ResourceError::InvalidAdvertisement)?
.to_vec();
let r = value
.map_get("r")
.and_then(|v| v.as_bin())
.ok_or(ResourceError::InvalidAdvertisement)?
.to_vec();
let o = value
.map_get("o")
.and_then(|v| v.as_bin())
.ok_or(ResourceError::InvalidAdvertisement)?
.to_vec();
let m = value
.map_get("m")
.and_then(|v| v.as_bin())
.ok_or(ResourceError::InvalidAdvertisement)?
.to_vec();
let f = value
.map_get("f")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)? as u8;
let i = value
.map_get("i")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)?;
let l = value
.map_get("l")
.and_then(|v| v.as_uint())
.ok_or(ResourceError::InvalidAdvertisement)?;
let q_val = value
.map_get("q")
.ok_or(ResourceError::InvalidAdvertisement)?;
let request_id = if q_val.is_nil() {
None
} else {
Some(
q_val
.as_bin()
.ok_or(ResourceError::InvalidAdvertisement)?
.to_vec(),
)
};
Ok(ResourceAdvertisement {
transfer_size: t,
data_size: d,
num_parts: n,
resource_hash: h,
random_hash: r,
original_hash: o,
hashmap: m,
flags: AdvFlags::from_byte(f),
segment_index: i,
total_segments: l,
request_id,
})
}
pub fn is_request(&self) -> bool {
self.request_id.is_some() && self.flags.is_request
}
pub fn is_response(&self) -> bool {
self.request_id.is_some() && self.flags.is_response
}
pub fn hashmap_segments(&self) -> usize {
let total_hashes = self.num_parts as usize;
if total_hashes == 0 {
return 1;
}
total_hashes.div_ceil(RESOURCE_HASHMAP_MAX_LEN)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_adv(flags: AdvFlags) -> ResourceAdvertisement {
ResourceAdvertisement {
transfer_size: 1000,
data_size: 950,
num_parts: 3,
resource_hash: vec![0x11; 32],
random_hash: vec![0xAA, 0xBB, 0xCC, 0xDD],
original_hash: vec![0x22; 32],
hashmap: vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
],
flags,
segment_index: 1,
total_segments: 1,
request_id: None,
}
}
#[test]
fn test_pack_unpack_roundtrip() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: false,
has_metadata: false,
};
let adv = make_adv(flags);
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert_eq!(unpacked.transfer_size, 1000);
assert_eq!(unpacked.data_size, 950);
assert_eq!(unpacked.num_parts, 3);
assert_eq!(unpacked.resource_hash, vec![0x11; 32]);
assert_eq!(unpacked.random_hash, vec![0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(unpacked.original_hash, vec![0x22; 32]);
assert_eq!(unpacked.flags, flags);
assert_eq!(unpacked.segment_index, 1);
assert_eq!(unpacked.total_segments, 1);
assert!(unpacked.request_id.is_none());
}
#[test]
fn test_flags_encrypted_compressed() {
let flags = AdvFlags {
encrypted: true,
compressed: true,
split: false,
is_request: false,
is_response: false,
has_metadata: false,
};
let adv = make_adv(flags);
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert!(unpacked.flags.encrypted);
assert!(unpacked.flags.compressed);
assert!(!unpacked.flags.split);
}
#[test]
fn test_flags_with_metadata() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: false,
has_metadata: true,
};
let adv = make_adv(flags);
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert!(unpacked.flags.has_metadata);
}
#[test]
fn test_multi_segment() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: true,
is_request: false,
is_response: false,
has_metadata: false,
};
let mut adv = make_adv(flags);
adv.segment_index = 2;
adv.total_segments = 5;
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert!(unpacked.flags.split);
assert_eq!(unpacked.segment_index, 2);
assert_eq!(unpacked.total_segments, 5);
}
#[test]
fn test_with_request_id() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: true,
is_response: false,
has_metadata: false,
};
let mut adv = make_adv(flags);
adv.request_id = Some(vec![0xDE, 0xAD, 0xBE, 0xEF]);
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert!(unpacked.is_request());
assert!(!unpacked.is_response());
assert_eq!(unpacked.request_id, Some(vec![0xDE, 0xAD, 0xBE, 0xEF]));
}
#[test]
fn test_is_response() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: true,
has_metadata: false,
};
let mut adv = make_adv(flags);
adv.request_id = Some(vec![0x42; 16]);
assert!(adv.is_response());
assert!(!adv.is_request());
}
#[test]
fn test_nil_request_id() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: false,
has_metadata: false,
};
let adv = make_adv(flags);
let packed = adv.pack(0);
let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
assert!(unpacked.request_id.is_none());
assert!(!unpacked.is_request());
assert!(!unpacked.is_response());
}
#[test]
fn test_hashmap_segmentation() {
let num_hashes = 100;
let hashmap: Vec<u8> = (0..num_hashes).flat_map(|i| vec![i as u8; 4]).collect();
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: false,
has_metadata: false,
};
let adv = ResourceAdvertisement {
transfer_size: 50000,
data_size: 48000,
num_parts: num_hashes,
resource_hash: vec![0x11; 32],
random_hash: vec![0xAA; 4],
original_hash: vec![0x22; 32],
hashmap: hashmap.clone(),
flags,
segment_index: 1,
total_segments: 1,
request_id: None,
};
let packed0 = adv.pack(0);
let unpacked0 = ResourceAdvertisement::unpack(&packed0).unwrap();
assert_eq!(unpacked0.hashmap.len(), 74 * 4);
let packed1 = adv.pack(1);
let unpacked1 = ResourceAdvertisement::unpack(&packed1).unwrap();
assert_eq!(unpacked1.hashmap.len(), 26 * 4);
}
#[test]
fn test_hashmap_segments_count() {
let flags = AdvFlags {
encrypted: true,
compressed: false,
split: false,
is_request: false,
is_response: false,
has_metadata: false,
};
let mut adv = make_adv(flags);
adv.num_parts = 74; assert_eq!(adv.hashmap_segments(), 1);
adv.num_parts = 75;
assert_eq!(adv.hashmap_segments(), 2);
adv.num_parts = 148;
assert_eq!(adv.hashmap_segments(), 2);
adv.num_parts = 149;
assert_eq!(adv.hashmap_segments(), 3);
}
#[test]
fn test_unpack_invalid_data() {
assert!(ResourceAdvertisement::unpack(&[]).is_err());
assert!(ResourceAdvertisement::unpack(&[0xc0]).is_err()); assert!(ResourceAdvertisement::unpack(&[0x01, 0x02]).is_err()); }
}