use crate::wire::packet::{Metadata, Packet, PacketEncryption, PacketType};
use over_there_auth::Signer;
use over_there_derive::Error;
use std::collections::HashMap;
pub(crate) struct DisassembleInfo<'d, 's, S: Signer> {
pub id: u32,
pub encryption: PacketEncryption,
pub desired_chunk_size: usize,
pub signer: &'s S,
pub data: &'d [u8],
}
#[derive(Debug, Error)]
pub enum DisassemblerError {
DesiredChunkSizeTooSmall,
FailedToEstimatePacketSize,
FailedToSignPacket,
}
#[derive(Debug, Clone)]
pub(crate) struct Disassembler {
packet_overhead_size_cache: HashMap<String, usize>,
}
impl Disassembler {
pub fn make_packets_from_data<S: Signer>(
&mut self,
info: DisassembleInfo<S>,
) -> Result<Vec<Packet>, DisassemblerError> {
let DisassembleInfo {
id,
encryption,
desired_chunk_size,
signer,
data,
} = info;
let non_final_overhead_size = self
.cached_estimate_packet_overhead_size(
desired_chunk_size,
PacketType::NotFinal,
signer,
)
.map_err(|_| DisassemblerError::FailedToEstimatePacketSize)?;
let final_overhead_size = self
.cached_estimate_packet_overhead_size(
desired_chunk_size,
PacketType::Final { encryption },
signer,
)
.map_err(|_| DisassemblerError::FailedToEstimatePacketSize)?;
if non_final_overhead_size >= desired_chunk_size
|| final_overhead_size >= desired_chunk_size
{
return Err(DisassemblerError::DesiredChunkSizeTooSmall);
}
let non_final_chunk_size = desired_chunk_size - non_final_overhead_size;
let final_chunk_size = desired_chunk_size - final_overhead_size;
let mut packets = Vec::new();
let mut i = 0;
while i < data.len() {
let can_fit_all_in_final_packet =
i + final_chunk_size >= data.len();
let can_fit_all_in_non_final_packet =
i + non_final_chunk_size >= data.len();
let chunk_size = if can_fit_all_in_final_packet
|| can_fit_all_in_non_final_packet
{
final_chunk_size
} else {
non_final_chunk_size
};
let chunk_size = std::cmp::min(chunk_size, data.len() - i);
let chunk = &data[i..i + chunk_size];
let packet = Self::make_new_packet(
id,
packets.len() as u32,
if can_fit_all_in_final_packet {
PacketType::Final { encryption }
} else {
PacketType::NotFinal
},
chunk,
signer,
)
.map_err(|_| DisassemblerError::FailedToSignPacket)?;
packets.push(packet);
i += chunk_size;
}
Ok(packets)
}
fn make_new_packet<S: Signer>(
id: u32,
index: u32,
r#type: PacketType,
data: &[u8],
signer: &S,
) -> Result<Packet, rmp_serde::encode::Error> {
let metadata = Metadata { id, index, r#type };
metadata.to_vec().map(|md| {
let sig = signer.sign(&[md, data.to_vec()].concat());
Packet::new(metadata, sig, data.to_vec())
})
}
fn cached_estimate_packet_overhead_size<S: Signer>(
&mut self,
desired_data_size: usize,
r#type: PacketType,
signer: &S,
) -> Result<usize, rmp_serde::encode::Error> {
let key = format!("{}{:?}", desired_data_size, r#type);
if let Some(value) = self.packet_overhead_size_cache.get(&key) {
return Ok(*value);
}
let overhead_size = Self::estimate_packet_overhead_size(
desired_data_size,
r#type,
signer,
)?;
self.packet_overhead_size_cache.insert(key, overhead_size);
Ok(overhead_size)
}
pub(crate) fn estimate_packet_overhead_size<S: Signer>(
desired_data_size: usize,
r#type: PacketType,
signer: &S,
) -> Result<usize, rmp_serde::encode::Error> {
let packet_size =
Self::estimate_packet_size(desired_data_size, r#type, signer)?;
Ok(if packet_size > desired_data_size {
packet_size - desired_data_size
} else {
0
})
}
fn estimate_packet_size<S: Signer>(
desired_data_size: usize,
r#type: PacketType,
signer: &S,
) -> Result<usize, rmp_serde::encode::Error> {
let fake_data: Vec<u8> = (0..desired_data_size)
.map(|_| rand::random::<u8>())
.collect();
Disassembler::make_new_packet(
u32::max_value(),
u32::max_value(),
r#type,
&fake_data,
signer,
)?
.to_vec()
.map(|v| v.len())
}
}
impl Default for Disassembler {
fn default() -> Self {
Self {
packet_overhead_size_cache: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use over_there_auth::NoopAuthenticator;
use over_there_crypto::{nonce, Nonce};
#[test]
fn fails_if_desired_chunk_size_is_too_low() {
let chunk_size = 1;
let err = Disassembler::default()
.make_packets_from_data(DisassembleInfo {
id: 0,
encryption: PacketEncryption::None,
data: &vec![1, 2, 3],
desired_chunk_size: chunk_size,
signer: &NoopAuthenticator,
})
.unwrap_err();
match err {
DisassemblerError::DesiredChunkSizeTooSmall => (),
x => panic!("Unexpected error: {:?}", x),
}
}
#[test]
fn produces_single_packet_with_data() {
let id = 12345;
let data: Vec<u8> = vec![1, 2];
let encryption =
PacketEncryption::from(Nonce::from(nonce::new_128bit_nonce()));
let chunk_size = 1000;
let packets = Disassembler::default()
.make_packets_from_data(DisassembleInfo {
id,
encryption,
data: &data,
desired_chunk_size: chunk_size,
signer: &NoopAuthenticator,
})
.unwrap();
assert_eq!(packets.len(), 1, "More than one packet produced");
let p = &packets[0];
assert_eq!(p.id(), id, "ID not properly set on packet");
assert_eq!(p.index(), 0, "Unexpected index for single packet");
assert_eq!(
p.is_final(),
true,
"Single packet not marked as last packet"
);
assert_eq!(p.data(), &data);
}
#[test]
fn produces_multiple_packets_with_data() {
let id = 67890;
let data: Vec<u8> = vec![1, 2, 3];
let nonce = Nonce::from(nonce::new_128bit_nonce());
let overhead_size = Disassembler::estimate_packet_overhead_size(
1,
PacketType::Final {
encryption: PacketEncryption::from(nonce),
},
&NoopAuthenticator,
)
.unwrap();
let chunk_size = overhead_size + 2;
let packets = Disassembler::default()
.make_packets_from_data(DisassembleInfo {
id,
encryption: PacketEncryption::from(nonce),
data: &data,
desired_chunk_size: chunk_size,
signer: &NoopAuthenticator,
})
.unwrap();
assert_eq!(packets.len(), 2, "Unexpected number of packets");
let p1 = packets.get(0).unwrap();
assert_eq!(p1.id(), id, "ID not properly set on first packet");
assert_eq!(p1.index(), 0, "First packet not marked with index 0");
assert_eq!(
p1.is_final(),
false,
"Non-final packet unexpectedly marked as last"
);
assert_eq!(&p1.data()[..], &data[0..2]);
let p2 = packets.get(1).unwrap();
assert_eq!(p2.id(), id, "ID not properly set on second packet");
assert_eq!(p2.index(), 1, "Last packet not marked with correct index");
assert_eq!(p2.is_final(), true, "Last packet not marked as final");
assert_eq!(&p2.data()[..], &data[2..]);
}
#[test]
fn produces_multiple_packets_respecting_size_constraints() {
let id = 67890;
let encryption =
PacketEncryption::from(Nonce::from(nonce::new_128bit_nonce()));
let data: Vec<u8> = [0; 100000].to_vec();
let chunk_size = 512;
let packets = Disassembler::default()
.make_packets_from_data(DisassembleInfo {
id,
encryption,
data: &data,
desired_chunk_size: chunk_size,
signer: &NoopAuthenticator,
})
.unwrap();
for (i, p) in packets.iter().enumerate() {
let actual_size = p.to_vec().unwrap().len();
assert!(
actual_size <= chunk_size,
"Serialized packet {}/{} was {} bytes instead of max size of {}",
i + 1,
packets.len(),
actual_size,
chunk_size
);
}
}
}