use crate::{ArchiveFormat, Block};
pub const NAME_RANGE: std::ops::Range<usize> = 0..100;
pub const MODE_RANGE: std::ops::Range<usize> = 100..108;
pub const UID_RANGE: std::ops::Range<usize> = 108..116;
pub const GID_RANGE: std::ops::Range<usize> = 116..124;
pub const SIZE_RANGE: std::ops::Range<usize> = 124..136;
pub const MTIME_RANGE: std::ops::Range<usize> = 136..148;
pub const CHECKSUM_RANGE: std::ops::Range<usize> = 148..156;
pub const TYPEFLAG_OFFSET: usize = 156;
pub const LINK_NAME_RANGE: std::ops::Range<usize> = 157..257;
pub const IDENTITY_RANGE: std::ops::Range<usize> = 257..265;
pub const UNAME_RANGE: std::ops::Range<usize> = 265..297;
pub const GNAME_RANGE: std::ops::Range<usize> = 297..329;
pub const DEVMAJOR_RANGE: std::ops::Range<usize> = 329..337;
pub const DEVMINOR_RANGE: std::ops::Range<usize> = 337..345;
pub const PREFIX_RANGE: std::ops::Range<usize> = 345..500;
pub const USTAR_IDENTITY: &[u8; 8] = b"ustar\x0000";
pub const GNU_IDENTITY: &[u8; 8] = b"ustar \0";
const MAX_CHECKSUM: u64 = (504 * 255) + (8 * 32);
const _: () = assert!(MAX_CHECKSUM < 0o777777);
#[inline(always)]
pub(crate) fn checksum(block: &Block) -> u64 {
let block_sum = block.iter().map(|byte| u32::from(*byte)).sum::<u32>();
let checksum_sum = block[CHECKSUM_RANGE]
.iter()
.map(|byte| u32::from(*byte))
.sum::<u32>();
u64::from(block_sum - checksum_sum + CHECKSUM_RANGE.len() as u32 * u32::from(b' '))
}
#[inline(always)]
pub(crate) fn encode_checksum(block: &mut Block) {
let value = checksum(block);
debug_assert!(value <= MAX_CHECKSUM);
let _ = encode_octal_with_suffix(&mut block[CHECKSUM_RANGE], value, b"\0 ");
}
pub(crate) fn encode_octal(field: &mut [u8], value: u64) -> bool {
encode_octal_with_suffix(field, value, b"\0")
}
fn encode_octal_with_suffix(field: &mut [u8], value: u64, suffix: &[u8]) -> bool {
let Some(width) = field.len().checked_sub(suffix.len()) else {
return false;
};
if width == 0 {
return false;
}
field[width..].copy_from_slice(suffix);
encode_octal_digits(&mut field[..width], value)
}
fn encode_octal_digits(field: &mut [u8], mut value: u64) -> bool {
for byte in field.iter_mut().rev() {
*byte = b'0' + (value & 0o7) as u8;
value >>= 3;
}
value == 0
}
pub(crate) fn parse_octal(bytes: &[u8]) -> Option<u64> {
let mut value = 0_u64;
let mut has_digits = false;
let mut terminated = false;
for byte in bytes {
match *byte {
b'0'..=b'7' if !terminated => {
value = value.checked_mul(8)?.checked_add(u64::from(*byte - b'0'))?;
has_digits = true;
}
0 | b' ' => terminated = true,
_ => return None,
}
}
(has_digits && terminated).then_some(value)
}
pub(crate) fn is_all_nul(bytes: &[u8]) -> bool {
bytes.iter().all(|byte| *byte == 0)
}
pub(crate) fn parse_number(format: ArchiveFormat, bytes: &[u8]) -> Option<u64> {
match format {
ArchiveFormat::Pax => parse_octal(bytes),
ArchiveFormat::Gnu => parse_gnu_number(bytes),
}
}
fn parse_gnu_number(bytes: &[u8]) -> Option<u64> {
match bytes.first()? {
0x80 => bytes[1..].iter().try_fold(0_u64, |value, byte| {
value.checked_mul(256)?.checked_add(u64::from(*byte))
}),
0xff => None,
_ => parse_octal(bytes),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encodes_octal_values_that_fit_the_field() {
let mut field = [0xff; 4];
assert!(encode_octal(&mut field, 0o17));
assert_eq!(&field, b"017\0");
assert_eq!(parse_octal(&field), Some(0o17));
assert!(encode_octal(&mut field, 0o777));
assert_eq!(&field, b"777\0");
assert!(!encode_octal(&mut field, 0o1000));
assert!(!encode_octal(&mut [], 0));
assert!(!encode_octal(&mut [0], 0));
}
#[test]
fn parses_strict_octal_fields() {
for (field, expected) in [
(&b"000017 "[..], Some(0o17)),
(&b"0000000000000000000000000000017 "[..], Some(0o17)),
(&b"17\0"[..], Some(0o17)),
(&b"17 "[..], Some(0o17)),
(&b"17 \0"[..], Some(0o17)),
(&b""[..], None),
(&b"\0"[..], None),
(&b" "[..], None),
(&b"17"[..], None),
(&b"18\0"[..], None),
(&b"1\0\x32"[..], None),
(&b"1\0\x31"[..], None),
(&b"1 1"[..], None),
(&[0x80, 0][..], None),
(&b"77777777777777777777777 "[..], None),
(&b"77777777777777777777777\0"[..], None),
] {
assert_eq!(parse_octal(field), expected, "{field:?}");
}
}
#[test]
fn checksums_known_blocks() {
let zero_block = [0; crate::BLOCK_SIZE];
let mut x_typeflag_block = zero_block;
x_typeflag_block[TYPEFLAG_OFFSET] = b'x';
x_typeflag_block[CHECKSUM_RANGE].fill(0xff);
let maximum_block = [0xff; crate::BLOCK_SIZE];
for (name, mut block, expected) in [
("zero block", zero_block, b"000400\0 "),
(
"x typeflag with junk checksum bytes",
x_typeflag_block,
b"000570\0 ",
),
("maximum block", maximum_block, b"373410\0 "),
] {
assert_eq!(Some(checksum(&block)), parse_octal(expected), "{name}");
encode_checksum(&mut block);
assert_eq!(&block[CHECKSUM_RANGE], expected, "{name}");
}
}
}