use crate::error::{Error, Result};
#[derive(Debug)]
pub struct ByteRangeCalculator {
placeholder_size: usize,
}
impl ByteRangeCalculator {
pub fn new(estimated_signature_size: usize) -> Self {
let placeholder_size = estimated_signature_size * 2 + 2;
Self { placeholder_size }
}
pub fn with_placeholder_size(placeholder_size: usize) -> Self {
Self { placeholder_size }
}
pub fn placeholder_size(&self) -> usize {
self.placeholder_size
}
pub fn generate_placeholder(&self) -> String {
format!("<{}>", "0".repeat(self.placeholder_size - 2))
}
pub fn calculate_byte_range(&self, file_size: usize, contents_offset: usize) -> [i64; 4] {
let before_sig = contents_offset as i64;
let after_sig_start = (contents_offset + self.placeholder_size) as i64;
let after_sig_len = file_size as i64 - after_sig_start;
[0, before_sig, after_sig_start, after_sig_len]
}
pub fn format_byte_range(byte_range: &[i64; 4]) -> String {
format!("[{} {} {} {}]", byte_range[0], byte_range[1], byte_range[2], byte_range[3])
}
pub fn extract_signed_bytes(pdf_data: &[u8], byte_range: &[i64; 4]) -> Result<Vec<u8>> {
let offset1 = byte_range[0] as usize;
let length1 = byte_range[1] as usize;
let offset2 = byte_range[2] as usize;
let length2 = byte_range[3] as usize;
if offset1 + length1 > pdf_data.len() {
return Err(Error::InvalidPdf(format!(
"ByteRange first range exceeds file size: {} + {} > {}",
offset1,
length1,
pdf_data.len()
)));
}
if offset2 + length2 > pdf_data.len() {
return Err(Error::InvalidPdf(format!(
"ByteRange second range exceeds file size: {} + {} > {}",
offset2,
length2,
pdf_data.len()
)));
}
let mut signed_bytes = Vec::with_capacity(length1 + length2);
signed_bytes.extend_from_slice(&pdf_data[offset1..offset1 + length1]);
signed_bytes.extend_from_slice(&pdf_data[offset2..offset2 + length2]);
Ok(signed_bytes)
}
pub fn validate_byte_range(byte_range: &[i64; 4], file_size: usize) -> Result<()> {
let offset1 = byte_range[0];
let length1 = byte_range[1];
let offset2 = byte_range[2];
let length2 = byte_range[3];
if offset1 != 0 {
return Err(Error::InvalidPdf(format!("ByteRange must start at 0, got {}", offset1)));
}
let expected_end = file_size as i64;
let actual_end = offset2 + length2;
if actual_end != expected_end {
return Err(Error::InvalidPdf(format!(
"ByteRange must end at file size {}, got {}",
expected_end, actual_end
)));
}
if length1 > offset2 {
return Err(Error::InvalidPdf(format!(
"ByteRange first range ({}) overlaps with second range start ({})",
length1, offset2
)));
}
Ok(())
}
pub fn find_contents_offset(pdf_data: &[u8], sig_dict_offset: usize) -> Option<usize> {
let search_start = sig_dict_offset;
let search_end = (sig_dict_offset + 4096).min(pdf_data.len());
let search_window = &pdf_data[search_start..search_end];
let contents_pattern = b"/Contents";
let mut pos = 0;
while pos + contents_pattern.len() < search_window.len() {
if search_window[pos..].starts_with(contents_pattern) {
let after_contents = pos + contents_pattern.len();
for i in after_contents..search_window.len() {
let byte = search_window[i];
if byte == b'<' {
return Some(search_start + i);
}
if !matches!(byte, b' ' | b'\t' | b'\n' | b'\r') {
break;
}
}
}
pos += 1;
}
None
}
pub fn insert_signature(
&self,
pdf_data: &mut [u8],
contents_offset: usize,
signature_hex: &str,
) -> Result<()> {
let sig_len = signature_hex.len() + 2; if sig_len > self.placeholder_size {
return Err(Error::InvalidPdf(format!(
"Signature ({} bytes) exceeds placeholder size ({} bytes)",
sig_len, self.placeholder_size
)));
}
let mut sig_value = String::with_capacity(self.placeholder_size);
sig_value.push('<');
sig_value.push_str(signature_hex);
let padding_needed = (self.placeholder_size - 2) - signature_hex.len();
for _ in 0..padding_needed {
sig_value.push('0');
}
sig_value.push('>');
if contents_offset + self.placeholder_size > pdf_data.len() {
return Err(Error::InvalidPdf(
"Signature insertion would exceed file bounds".to_string(),
));
}
pdf_data[contents_offset..contents_offset + self.placeholder_size]
.copy_from_slice(sig_value.as_bytes());
Ok(())
}
}
impl Default for ByteRangeCalculator {
fn default() -> Self {
Self::new(8192)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_placeholder_size() {
let calc = ByteRangeCalculator::new(1024);
assert_eq!(calc.placeholder_size(), 2050);
}
#[test]
fn test_generate_placeholder() {
let calc = ByteRangeCalculator::with_placeholder_size(10);
let placeholder = calc.generate_placeholder();
assert_eq!(placeholder, "<00000000>");
assert_eq!(placeholder.len(), 10);
}
#[test]
fn test_calculate_byte_range() {
let calc = ByteRangeCalculator::with_placeholder_size(100);
let byte_range = calc.calculate_byte_range(1000, 400);
assert_eq!(byte_range[0], 0); assert_eq!(byte_range[1], 400); assert_eq!(byte_range[2], 500); assert_eq!(byte_range[3], 500); }
#[test]
fn test_format_byte_range() {
let byte_range = [0, 100, 200, 300];
let formatted = ByteRangeCalculator::format_byte_range(&byte_range);
assert_eq!(formatted, "[0 100 200 300]");
}
#[test]
fn test_extract_signed_bytes() {
let pdf_data = b"AAABBBCCC"; let byte_range = [0, 3, 6, 3];
let signed = ByteRangeCalculator::extract_signed_bytes(pdf_data, &byte_range).unwrap();
assert_eq!(signed, b"AAACCC");
}
#[test]
fn test_validate_byte_range_valid() {
let byte_range = [0, 100, 150, 50];
let result = ByteRangeCalculator::validate_byte_range(&byte_range, 200);
assert!(result.is_ok());
}
#[test]
fn test_validate_byte_range_invalid_start() {
let byte_range = [10, 100, 150, 50];
let result = ByteRangeCalculator::validate_byte_range(&byte_range, 200);
assert!(result.is_err());
}
#[test]
fn test_validate_byte_range_invalid_end() {
let byte_range = [0, 100, 150, 100];
let result = ByteRangeCalculator::validate_byte_range(&byte_range, 200);
assert!(result.is_err());
}
#[test]
fn test_insert_signature() {
let calc = ByteRangeCalculator::with_placeholder_size(10);
let mut pdf_data = b"XX<00000000>YY".to_vec();
let contents_offset = 2;
let signature_hex = "ABCD";
calc.insert_signature(&mut pdf_data, contents_offset, signature_hex)
.unwrap();
assert_eq!(&pdf_data, b"XX<ABCD0000>YY");
}
#[test]
fn test_insert_signature_too_large() {
let calc = ByteRangeCalculator::with_placeholder_size(10);
let mut pdf_data = b"XX<00000000>YY".to_vec();
let signature_hex = "AABBCCDDEE";
let result = calc.insert_signature(&mut pdf_data, 2, signature_hex);
assert!(result.is_err());
}
}