use crate::error::Result;
use crate::pack::{Pack, ReadCursor, Unpack, WriteCursor};
use crate::Error;
pub const RESUME_KEY_LEN: usize = 24;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SrvCopychunk {
pub source_offset: u64,
pub target_offset: u64,
pub length: u32,
}
impl SrvCopychunk {
pub const SIZE: usize = 24;
}
impl Pack for SrvCopychunk {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u64_le(self.source_offset);
cursor.write_u64_le(self.target_offset);
cursor.write_u32_le(self.length);
cursor.write_u32_le(0); }
}
impl Unpack for SrvCopychunk {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let source_offset = cursor.read_u64_le()?;
let target_offset = cursor.read_u64_le()?;
let length = cursor.read_u32_le()?;
let _reserved = cursor.read_u32_le()?;
Ok(SrvCopychunk {
source_offset,
target_offset,
length,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SrvCopychunkCopy {
pub source_key: [u8; RESUME_KEY_LEN],
pub chunks: Vec<SrvCopychunk>,
}
impl SrvCopychunkCopy {
pub const HEADER_SIZE: usize = RESUME_KEY_LEN + 8;
#[must_use]
pub fn packed_size(chunk_count: usize) -> usize {
Self::HEADER_SIZE + chunk_count * SrvCopychunk::SIZE
}
}
impl Pack for SrvCopychunkCopy {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_bytes(&self.source_key);
cursor.write_u32_le(self.chunks.len() as u32);
cursor.write_u32_le(0); for chunk in &self.chunks {
chunk.pack(cursor);
}
}
}
impl Unpack for SrvCopychunkCopy {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let key_bytes = cursor.read_bytes(RESUME_KEY_LEN)?;
let mut source_key = [0u8; RESUME_KEY_LEN];
source_key.copy_from_slice(key_bytes);
let chunk_count = cursor.read_u32_le()?;
let _reserved = cursor.read_u32_le()?;
let mut chunks = Vec::with_capacity(chunk_count as usize);
for _ in 0..chunk_count {
chunks.push(SrvCopychunk::unpack(cursor)?);
}
Ok(SrvCopychunkCopy { source_key, chunks })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SrvCopychunkResponse {
pub chunks_written: u32,
pub chunk_bytes_written: u32,
pub total_bytes_written: u32,
}
impl SrvCopychunkResponse {
pub const SIZE: usize = 12;
}
impl Pack for SrvCopychunkResponse {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u32_le(self.chunks_written);
cursor.write_u32_le(self.chunk_bytes_written);
cursor.write_u32_le(self.total_bytes_written);
}
}
impl Unpack for SrvCopychunkResponse {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let chunks_written = cursor.read_u32_le()?;
let chunk_bytes_written = cursor.read_u32_le()?;
let total_bytes_written = cursor.read_u32_le()?;
Ok(SrvCopychunkResponse {
chunks_written,
chunk_bytes_written,
total_bytes_written,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SrvRequestResumeKeyResponse {
pub resume_key: [u8; RESUME_KEY_LEN],
}
impl Pack for SrvRequestResumeKeyResponse {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_bytes(&self.resume_key);
cursor.write_u32_le(0); }
}
impl Unpack for SrvRequestResumeKeyResponse {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let key_bytes = cursor.read_bytes(RESUME_KEY_LEN).map_err(|_| {
Error::invalid_data("SRV_REQUEST_RESUME_KEY response shorter than 24-byte resume key")
})?;
let mut resume_key = [0u8; RESUME_KEY_LEN];
resume_key.copy_from_slice(key_bytes);
Ok(SrvRequestResumeKeyResponse { resume_key })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn copychunk_roundtrip() {
let original = SrvCopychunk {
source_offset: 0x1122_3344_5566_7788,
target_offset: 0x00AA_BB00_CC00_DD00,
length: 0x0010_0000,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), SrvCopychunk::SIZE);
let mut r = ReadCursor::new(&bytes);
let decoded = SrvCopychunk::unpack(&mut r).unwrap();
assert_eq!(decoded, original);
assert!(r.is_empty());
}
#[test]
fn copychunk_copy_roundtrip_multi_chunk() {
let original = SrvCopychunkCopy {
source_key: [0x5A; RESUME_KEY_LEN],
chunks: vec![
SrvCopychunk {
source_offset: 0,
target_offset: 0,
length: 1024,
},
SrvCopychunk {
source_offset: 1024,
target_offset: 4096,
length: 2048,
},
],
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), SrvCopychunkCopy::packed_size(2));
let mut r = ReadCursor::new(&bytes);
let decoded = SrvCopychunkCopy::unpack(&mut r).unwrap();
assert_eq!(decoded, original);
assert!(r.is_empty());
}
#[test]
fn copychunk_copy_chunk_count_is_derived_from_len() {
let copy = SrvCopychunkCopy {
source_key: [0; RESUME_KEY_LEN],
chunks: vec![SrvCopychunk {
source_offset: 0,
target_offset: 0,
length: 8,
}],
};
let mut w = WriteCursor::new();
copy.pack(&mut w);
let bytes = w.into_inner();
let chunk_count = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
assert_eq!(chunk_count, 1);
}
#[test]
fn copychunk_response_roundtrip() {
let original = SrvCopychunkResponse {
chunks_written: 3,
chunk_bytes_written: 0,
total_bytes_written: 3 * 1024 * 1024,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), SrvCopychunkResponse::SIZE);
let mut r = ReadCursor::new(&bytes);
let decoded = SrvCopychunkResponse::unpack(&mut r).unwrap();
assert_eq!(decoded, original);
assert!(r.is_empty());
}
#[test]
fn resume_key_response_roundtrip_ignores_trailing_context() {
let key = [0x37; RESUME_KEY_LEN];
let mut w = WriteCursor::new();
SrvRequestResumeKeyResponse { resume_key: key }.pack(&mut w);
let mut bytes = w.into_inner();
bytes.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let mut r = ReadCursor::new(&bytes);
let decoded = SrvRequestResumeKeyResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.resume_key, key);
}
#[test]
fn resume_key_response_rejects_short_buffer() {
let short = [0u8; RESUME_KEY_LEN - 1];
let mut r = ReadCursor::new(&short);
assert!(SrvRequestResumeKeyResponse::unpack(&mut r).is_err());
}
}
#[cfg(test)]
mod roundtrip_props {
use super::*;
use proptest::prelude::*;
fn arb_chunk() -> impl Strategy<Value = SrvCopychunk> {
(any::<u64>(), any::<u64>(), any::<u32>()).prop_map(
|(source_offset, target_offset, length)| SrvCopychunk {
source_offset,
target_offset,
length,
},
)
}
proptest! {
#[test]
fn copychunk_copy_pack_unpack(
source_key in any::<[u8; RESUME_KEY_LEN]>(),
chunks in prop::collection::vec(arb_chunk(), 0..8),
) {
let original = SrvCopychunkCopy { source_key, chunks };
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = SrvCopychunkCopy::unpack(&mut r).unwrap();
prop_assert_eq!(decoded, original);
prop_assert!(r.is_empty());
}
#[test]
fn copychunk_response_pack_unpack(
chunks_written in any::<u32>(),
chunk_bytes_written in any::<u32>(),
total_bytes_written in any::<u32>(),
) {
let original = SrvCopychunkResponse {
chunks_written,
chunk_bytes_written,
total_bytes_written,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = SrvCopychunkResponse::unpack(&mut r).unwrap();
prop_assert_eq!(decoded, original);
prop_assert!(r.is_empty());
}
}
}