use std::{io, time, fmt};
use pad::{PadStr, Alignment};
use num;
use num::ToPrimitive;
use num_traits;
pub fn format_gnu_numeral<N: num::Integer>(number: N, field_size: usize) -> Option<Vec<u8>> where N: fmt::Octal + num::traits::CheckedShr + std::ops::BitAnd + num_traits::cast::ToPrimitive + From<u8>, <N as std::ops::BitAnd>::Output: num_traits::cast::ToPrimitive {
let numsize = number.to_f32()?.log(8.0);
let gnusize = number.to_f32()?.log(256.0);
if gnusize >= (field_size as f32 - 1.0) {
None
} else if numsize >= (field_size as f32 - 1.0) {
let mut result : Vec<u8> = vec![0; field_size];
result[0] = 0x80;
for i in 0..(field_size - 1) {
result[field_size - i - 1] = ((number.checked_shr(i as u32 * 8).unwrap_or(N::from(0))) & N::from(0xFF)).to_u8().unwrap();
}
Some(result)
} else {
let mut value = format!("{:o}", number).pad(field_size - 1, '0', Alignment::Right, true).into_bytes();
value.push(0);
Some(value)
}
}
pub fn format_gnu_time(dirtime: &time::SystemTime) -> io::Result<Vec<u8>> {
match dirtime.duration_since(time::UNIX_EPOCH) {
Ok(unix_duration) => format_gnu_numeral(unix_duration.as_secs(), 12).ok_or(io::Error::new(io::ErrorKind::InvalidData, "Tar numeral too large")),
Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "File older than UNIX")) }
}
#[cfg(test)]
mod tests {
use crate::tar::gnu::{format_gnu_numeral};
#[test]
fn format_gnu_numeral_8() {
assert_eq!(match format_gnu_numeral(0o755, 8) {
Some(x) => x,
None => vec![]
}, vec![0x30, 0x30, 0x30, 0x30, 0x37, 0x35, 0x35, 0x00]);
}
#[test]
fn format_gnu_numeral_8_large() {
assert_eq!(match format_gnu_numeral(0xDEADBE, 8) {
Some(x) => x,
None => vec![]
}, vec![0x80, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE]);
}
#[test]
fn format_gnu_numeral_8_verylarge() {
assert!(match format_gnu_numeral(0xDEADBEEFDEADBEEF as u64, 8) {
Some(_) => false,
None => true
});
}
}