#[cfg(doc)]
use futures_lite::io::BufReader;
use crate::error::{Result as ZipResult, ZipError};
use crate::spec::consts::{EOCDR_LENGTH, EOCDR_SIGNATURE, SIGNATURE_LENGTH};
use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom};
const BUFFER_SIZE: usize = 2048;
const EOCDR_UPPER_BOUND: u64 = EOCDR_LENGTH as u64;
const EOCDR_LOWER_BOUND: u64 = EOCDR_UPPER_BOUND + SIGNATURE_LENGTH as u64 + u16::MAX as u64;
pub async fn eocdr<R>(mut reader: R) -> ZipResult<u64>
where
R: AsyncRead + AsyncSeek + Unpin,
{
let length = reader.seek(SeekFrom::End(0)).await?;
let signature = &EOCDR_SIGNATURE.to_le_bytes();
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
let mut position = length.saturating_sub(BUFFER_SIZE as u64);
reader.seek(SeekFrom::Start(position)).await?;
loop {
let mut read = 0;
while read < BUFFER_SIZE {
let bytes_read = reader.read(&mut buffer[read..]).await?;
if bytes_read == 0 {
break;
}
read += bytes_read;
}
if let Some(match_index) = reverse_search_buffer(&buffer[..read], signature) {
return Ok(position + (match_index + 1) as u64);
}
if position == 0 || position <= length.saturating_sub(EOCDR_LOWER_BOUND) {
return Err(ZipError::UnableToLocateEOCDR);
}
position = position.saturating_sub((BUFFER_SIZE - SIGNATURE_LENGTH) as u64);
reader.seek(SeekFrom::Start(position)).await?;
}
}
pub(crate) fn reverse_search_buffer(buffer: &[u8], signature: &[u8]) -> Option<usize> {
'outer: for index in (0..buffer.len()).rev() {
for (signature_index, signature_byte) in signature.iter().rev().enumerate() {
if let Some(next_index) = index.checked_sub(signature_index) {
if buffer[next_index] != *signature_byte {
continue 'outer;
}
} else {
break 'outer;
}
}
return Some(index);
}
None
}