#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::{fmt::Write, num::Wrapping, slice};
use bytemuck::{NoUninit, AnyBitPattern, bytes_of_mut};
use crate::{
error::{LhaError, LhaResult, LhaHeaderError},
stub_io::Read,
crc::Crc16,
};
use super::*;
pub mod ext {
pub const EXT_HEADER_COMMON: u8 = 0x00;
pub const EXT_HEADER_FILENAME: u8 = 0x01;
pub const EXT_HEADER_PATH: u8 = 0x02;
pub const EXT_HEADER_MULTI_DISC: u8 = 0x39;
pub const EXT_HEADER_COMMENT: u8 = 0x3F;
pub const EXT_HEADER_MSDOS_ATTRS: u8 = 0x40;
pub const EXT_HEADER_WINDOWS_TIME: u8 = 0x41;
#[deprecated(note="please use `EXT_HEADER_WINDOWS_TIME` instead")]
pub const EXT_HEADER_MSDOS_TIME: u8 = EXT_HEADER_WINDOWS_TIME;
pub const EXT_HEADER_FILE_SIZES: u8 = 0x42;
#[deprecated(note="please use `EXT_HEADER_FILE_SIZES` instead")]
pub const EXT_HEADER_MSDOS_SIZE: u8 = EXT_HEADER_FILE_SIZES;
pub const EXT_HEADER_UNIX_PERM: u8 = 0x50;
pub const EXT_HEADER_UNIX_UIDGID: u8 = 0x51;
pub const EXT_HEADER_UNIX_GROUP: u8 = 0x52;
pub const EXT_HEADER_UNIX_OWNER: u8 = 0x53;
pub const EXT_HEADER_UNIX_TIME: u8 = 0x54;
pub const EXT_HEADER_MAC_CAPSULE: u8 = 0x7D;
pub const EXT_HEADER_OS2_ATTR1: u8 = 0x7E;
pub const EXT_HEADER_EXT_ATTRS: u8 = 0x7F;
pub const EXT_HEADER_OS9: u8 = 0xCC;
pub const EXT_HEADER_METADATA: u8 = 0x71;
}
use ext::*;
pub struct ExtraHeaderIter<'a> {
data: &'a [u8],
header_length: u32,
header_len32: bool
}
impl<'a> Iterator for ExtraHeaderIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
let header_length = self.header_length as usize;
if header_length == 0 {
return None
}
let (res, data) = self.data.split_at(header_length);
let (res, len) = if self.header_len32 {
res.split_last_chunk::<4>().map(|(dat, &len)|
(dat, u32::from_le_bytes(len)))
}
else {
res.split_last_chunk::<2>().map(|(dat, &len)|
(dat, u16::from_le_bytes(len).into()))
}.unwrap();
self.header_length = len;
self.data = data;
Some(res)
}
}
const ALLOCATE_LIMIT_MAX: usize = 8*1024;
#[derive(Clone, Copy, Debug, Default, NoUninit, AnyBitPattern)]
#[repr(C)]
#[repr(packed)]
struct LhaRawBaseHeader {
compression: [u8;5],
compressed_size: [u8;4],
original_size: [u8;4],
last_modified: [u8;4],
msdos_attrs: u8,
lha_level: u8
}
struct Parser<'a, R> {
rd: &'a mut R,
crc: Crc16,
csum: Wrapping<u8>,
len: usize
}
impl<R: Read> Parser<'_, R> {
fn read_u8_or_none(&mut self) -> LhaResult<Option<u8>, R> {
let mut byte = 0u8;
if 0 == self.rd.read_all(slice::from_mut(&mut byte)).map_err(LhaError::Io)? {
return Ok(None)
}
self.update_checksums_no_wrapping_sum(slice::from_ref(&byte));
Ok(Some(byte))
}
fn read_u8(&mut self) -> LhaResult<u8, R> {
let mut byte: u8 = 0;
self.read_exact(slice::from_mut(&mut byte))?;
Ok(byte)
}
fn read_u16(&mut self) -> LhaResult<u16, R> {
let mut buf = [0u8;2];
self.read_exact(&mut buf)?;
Ok(u16::from_le_bytes(buf))
}
fn read_u32(&mut self) -> LhaResult<u32, R> {
let mut buf = [0u8;4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_exact(&mut self, buf: &mut [u8]) -> LhaResult<(), R> {
self.rd.read_exact(buf).map_err(LhaError::Io)?;
self.update_checksums(buf);
Ok(())
}
fn read_limit(&mut self, limit: usize) -> LhaResult<Box<[u8]>, R> {
let mut buf = Vec::new();
self.read_limit_no_checksums(limit, &mut buf)?;
self.update_checksums(&buf);
Ok(buf.into_boxed_slice())
}
fn update_checksums(&mut self, data: &[u8]) {
self.update_checksums_no_wrapping_sum(data);
self.csum = wrapping_csum(self.csum, data);
}
fn update_checksums_no_wrapping_sum(&mut self, data: &[u8]) {
self.len += data.len();
self.crc.digest(data);
}
fn read_limit_no_checksums(&mut self, mut limit: usize, buf: &mut Vec<u8>) -> LhaResult<(), R> {
while limit != 0 {
let chunk_size = limit.min(ALLOCATE_LIMIT_MAX);
buf.try_reserve_exact(chunk_size).map_err(|err| LhaError::HeaderParse(err.into()))?;
let spare_uninit = &mut buf.spare_capacity_mut()[..chunk_size];
let spare = unsafe { spare_uninit.assume_init_mut() };
self.rd.read_exact(spare).map_err(LhaError::Io)?;
unsafe { buf.set_len(buf.len() + chunk_size); }
limit -= chunk_size;
}
Ok(())
}
}
impl LhaHeader {
pub fn read<R: Read>(rd: &mut R) -> LhaResult<Option<LhaHeader>, R> {
let mut parser = Parser {
rd,
crc: Crc16::default(),
csum: Wrapping(0),
len: 0
};
let header_len = match parser.read_u8_or_none()? {
Some(0)|None => return Ok(None),
Some(len) => len
};
let csum = parser.read_u8()?;
parser.csum = Wrapping(0);
let mut raw_header = LhaRawBaseHeader::default();
parser.read_exact(bytes_of_mut(&mut raw_header))?;
if raw_header.lha_level > 3 {
return Err(LhaError::HeaderParse(LhaHeaderError::UnknownLevel))
}
let filename = if raw_header.lha_level < 2 {
let filename_len = parser.read_u8()? as usize;
if (header_len as usize) < parser.len + filename_len {
return Err(LhaError::HeaderParse(LhaHeaderError::SizeMismatch))
}
parser.read_limit(filename_len)?
}
else {
Box::new([])
};
let file_crc = parser.read_u16()?;
let mut os_type = 0;
if raw_header.lha_level > 0 {
os_type = parser.read_u8()?;
}
let mut extended_area: Box<[u8]> = Box::new([]);
if raw_header.lha_level < 2 {
let mut min_len = parser.len;
if raw_header.lha_level == 0 {
min_len -= 2; }
let extended_len = (header_len as usize).checked_sub(min_len)
.ok_or(LhaHeaderError::SizeMismatch)?;
if extended_len != 0 {
extended_area = parser.read_limit(extended_len)?;
}
};
let mut long_header_len: u32 = 0; let mut first_header_len: u32 = 0;
match raw_header.lha_level {
1 => {
first_header_len = parser.read_u16()? as u32;
}
2 => {
long_header_len = u16::from_le_bytes([header_len, csum]) as u32;
first_header_len = parser.read_u16()? as u32;
}
3 => {
long_header_len = parser.read_u32()?;
first_header_len = parser.read_u32()?;
if header_len != 4 || csum != 0 {
return Err(LhaError::HeaderParse(LhaHeaderError::Level3Signature))
}
}
_ => {}
}
if raw_header.lha_level < 2 {
if csum != parser.csum.0 {
return Err(LhaError::HeaderParse(LhaHeaderError::WrappingSumMismatch))
}
}
else if (long_header_len.saturating_sub(first_header_len) as usize) < parser.len {
return Err(LhaError::HeaderParse(LhaHeaderError::LongSizeMismatch))
}
let mut extra_headers = Vec::new();
let mut msdos_attrs = MsDosAttrs::from_bits_retain(raw_header.msdos_attrs as u16);
let mut original_size = u32::from_le_bytes(raw_header.original_size) as u64;
let mut compressed_size = u32::from_le_bytes(raw_header.compressed_size) as u64;
let mut header_crc: Option<u16> = None;
let mut extra_header_len = first_header_len as usize;
while extra_header_len != 0 {
if long_header_len != 0 {
if (long_header_len as usize).saturating_sub(extra_header_len) < parser.len - 2 {
return Err(LhaError::HeaderParse(LhaHeaderError::LongSizeMismatch))
}
}
else if compressed_size < (extra_headers.len() as u64) + extra_header_len as u64 {
return Err(LhaError::HeaderParse(LhaHeaderError::SkipSizeMismatch))
}
parser.read_limit_no_checksums(extra_header_len, &mut extra_headers)?;
assert!(extra_header_len <= extra_headers.len());
let start = extra_headers.len() - extra_header_len;
let header_with_len = &mut extra_headers[start..];
{
let (header, next_header_len) = if raw_header.lha_level == 3 {
header_with_len.split_last_chunk_mut::<4>().map(|(h, chunk)|
(h, u32::from_le_bytes(*chunk) as usize))
}
else {
header_with_len.split_last_chunk_mut::<2>().map(|(h, chunk)|
(h, u16::from_le_bytes(*chunk) as usize))
}
.filter(|(h, _)| !h.is_empty())
.ok_or(LhaHeaderError::ExtendedHeaderSize)?;
match header {
[EXT_HEADER_COMMON, data @ ..] => {
if header_crc.is_some() {
return Err(LhaError::HeaderParse(LhaHeaderError::CommonHeader))
}
if let Some(crc) = data.get_mut(0..2) {
header_crc = read_u16(crc);
for p in crc.iter_mut() {
*p = 0;
}
}
}
[EXT_HEADER_MSDOS_ATTRS, data @ ..]|
[EXT_HEADER_EXT_ATTRS, data @ ..] if data.len() >= 2 => {
let attrs = read_u16(&data[0..2]).unwrap();
msdos_attrs = MsDosAttrs::from_bits_retain(attrs);
}
[EXT_HEADER_FILE_SIZES, data @ ..] if raw_header.lha_level >= 2 && data.len() >= 16 => {
compressed_size = read_u64(&data[0..8]).unwrap();
original_size = read_u64(&data[8..16]).unwrap();
}
_ => {}
}
extra_header_len = next_header_len;
}
parser.update_checksums_no_wrapping_sum(header_with_len);
}
if long_header_len != 0 &&
long_header_len as usize != parser.len
{
if raw_header.lha_level == 2 && (long_header_len as usize) - 1 == parser.len
{
parser.read_u8()?;
}
else if raw_header.lha_level != 2 || long_header_len as usize != parser.len - 2
{
return Err(LhaError::HeaderParse(LhaHeaderError::LongSizeMismatch))
}
}
if let Some(crc) = header_crc && crc != parser.crc.sum16() {
return Err(LhaError::HeaderParse(LhaHeaderError::Crc16Mismatch))
}
if raw_header.lha_level == 1 {
compressed_size = compressed_size.checked_sub(extra_headers.len() as u64)
.ok_or(LhaError::HeaderParse(LhaHeaderError::SkipSizeMismatch))?
}
let compression = raw_header.compression;
let last_modified = u32::from_le_bytes(raw_header.last_modified);
let extra_headers = extra_headers.into_boxed_slice();
Ok(Some(LhaHeader {
level: raw_header.lha_level,
compression,
compressed_size,
original_size,
filename,
os_type,
msdos_attrs,
last_modified,
file_crc,
extended_area,
first_header_len,
extra_headers
}))
}
pub fn iter_extra(&self) -> ExtraHeaderIter<'_> {
ExtraHeaderIter {
data: &self.extra_headers,
header_length: self.first_header_len,
header_len32: self.level == 3
}
}
}
#[inline]
pub(super) fn read_u16(slice: &[u8]) -> Option<u16> {
slice.as_array::<{size_of::<u16>()}>().copied().map(u16::from_le_bytes)
}
#[inline]
pub(super) fn read_u32(slice: &[u8]) -> Option<u32> {
slice.as_array::<{size_of::<u32>()}>().copied().map(u32::from_le_bytes)
}
#[inline]
pub(super) fn read_u64(slice: &[u8]) -> Option<u64> {
slice.as_array::<{size_of::<u64>()}>().copied().map(u64::from_le_bytes)
}
fn wrapping_csum(init: Wrapping<u8>, data: &[u8]) -> Wrapping<u8> {
let sum: Wrapping<u8> = data.iter().copied().map(Wrapping).sum();
sum + init
}
pub(super) fn split_data_at_nil_or_end(data: &[u8]) -> (&[u8], Option<&[u8]>) {
match memchr::memchr(0, data) {
Some(index) => {
#[cfg(all(not(feature = "no-unsafe-assertions"), not(debug_assertions)))]
unsafe {
core::hint::assert_unchecked(index < data.len());
}
(&data[0..index], Some(&data[index + 1..]))
}
None => (data, None)
}
}
#[cfg(feature = "std")]
pub(super) fn parse_pathname(data: &[u8], path: &mut PathBuf) {
path.reserve(data.len());
for part in data.split(|&c| matches!(c, 0xFF|b'/'|b'\\')) {
match part {
b"."|b".."|[] => {} name => path.push(parse_str_nilterm(name, false, false).as_ref())
}
}
}
pub(super) fn parse_pathname_to_str(data: &[u8], path: &mut String) {
path.reserve(data.len());
for part in data.split(|&c| matches!(c, 0xFF|b'/'|b'\\')) {
match part {
b"."|b".."|[] => {} name => {
if !path.is_empty() {
path.push('/');
}
path.push_str(parse_str_nilterm(name, false, false).as_ref())
}
}
}
}
#[inline(always)]
pub(super) fn is_separator(c: char) -> bool {
#[cfg(feature = "std")]
{
std::path::is_separator(c)
}
#[cfg(not(feature = "std"))]
{
matches!(c, '/'|'\\')
}
}
pub(super) fn parse_str_nilterm(
data: &[u8], nilterm: bool, ignore_sep: bool
) -> Cow<'_, str>
{
if let Some(index) = data.iter().position(|&c|
!(0x20..0x7f).contains(&c) ||
(!ignore_sep && is_separator(c as char))
)
{
let mut out = String::with_capacity(data.len()*3);
let (head, rest) = data.split_at(index);
out.push_str(unsafe {
core::str::from_utf8_unchecked(head)
});
for byte in rest.iter() {
match byte {
0 if nilterm => break,
0x00..=0x1f|
0x7f..=0xff => {
write!(out, "%{:02x}", byte).unwrap();
}
&ch => {
let c = ch as char;
if !ignore_sep && is_separator(c) {
out.push('_');
}
else {
out.push(c);
}
}
}
}
Cow::Owned(out)
}
else {
unsafe {
Cow::Borrowed(core::str::from_utf8_unchecked(data))
}
}
}
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
use std::path::MAIN_SEPARATOR;
fn parse_filename(data: &[u8]) -> Cow<'_, str> {
parse_str_nilterm(data, false, false)
}
#[test]
fn split_data_at_nil_or_end_works() {
assert_eq!((&b"Foo"[..], None), split_data_at_nil_or_end(b"Foo"));
assert_eq!((&b"Foo"[..], Some(&b"Bar"[..])), split_data_at_nil_or_end(b"Foo\x00Bar"));
assert_eq!((&[][..], Some(&b"Bar"[..])), split_data_at_nil_or_end(b"\x00Bar"));
}
#[test]
fn path_parser_works() {
assert_eq!("", parse_filename(b""));
assert_eq!("Hello World!", parse_filename(b"Hello World!"));
assert!(std::path::is_separator('/'));
assert_eq!("_Hello_World_", parse_filename(b"/Hello/World/"));
#[cfg(target_family = "windows")]
if std::path::is_separator('\\') {
assert_eq!("_Hello_World_", parse_filename(br"\Hello\World\"));
}
assert_eq!("Hello%00World%7f", parse_filename(b"Hello\x00World\x7f"));
assert_eq!("Hello%01World%ff", parse_filename(b"Hello\x01World\xff"));
assert_eq!("Hello", parse_str_nilterm(b"Hello\x00World\xff", true, false));
assert_eq!("He_llo", parse_str_nilterm(b"He/llo\x00World\xff", true, false));
assert_eq!("He/llo", parse_str_nilterm(b"He/llo\x00World\xff", true, true));
assert_eq!("He/llo%00World%ff", parse_str_nilterm(b"He/llo\x00World\xff", false, true));
assert_eq!("_Hello%1fWorld%80", parse_filename(b"/Hello\x1fWorld\x80"));
let mut path = PathBuf::new();
parse_pathname(b"", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(b"/", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"\", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br".", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"..", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"./..", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br".\..", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"/..\./", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"\../.\", &mut path);
assert!(path.is_relative());
assert_eq!("", path.to_str().unwrap());
parse_pathname(br"foo/bar\baz", &mut path);
assert!(path.is_relative());
let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
assert_eq!(expect, path.to_str().unwrap());
path.clear();
parse_pathname(br"\foo/bar\baz/", &mut path);
assert!(path.is_relative());
let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
assert_eq!(expect, path.to_str().unwrap());
path.clear();
parse_pathname(br"/foo\bar/baz\", &mut path);
assert!(path.is_relative());
let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
assert_eq!(expect, path.to_str().unwrap());
path.clear();
parse_pathname(b"foo\xffbar\xffbaz", &mut path);
assert!(path.is_relative());
let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
assert_eq!(expect, path.to_str().unwrap());
path.clear();
parse_pathname(b"\xfffoo\xffb\x91ar\xffbaz\xff", &mut path);
assert!(path.is_relative());
let expect = format!("foo{}b%91ar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
assert_eq!(expect, path.to_str().unwrap());
path.clear();
}
#[test]
fn path_parser_to_str_works() {
let mut path = String::new();
parse_pathname_to_str(b"", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(b"/", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"\", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br".", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"..", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"./..", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br".\..", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"/..\./", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"\../.\", &mut path);
assert!(!path.starts_with('/'));
assert_eq!("", &path);
parse_pathname_to_str(br"foo/bar\baz", &mut path);
assert!(!path.starts_with('/'));
let expect = "foo/bar/baz";
assert_eq!(expect, &path);
path.clear();
parse_pathname_to_str(br"\foo/bar\baz/", &mut path);
assert!(!path.starts_with('/'));
let expect = "foo/bar/baz";
assert_eq!(expect, &path);
path.clear();
parse_pathname_to_str(br"/foo\bar/baz\", &mut path);
assert!(!path.starts_with('/'));
let expect = "foo/bar/baz";
assert_eq!(expect, &path);
path.clear();
parse_pathname_to_str(b"foo\xffbar\xffbaz", &mut path);
assert!(!path.starts_with('/'));
let expect = "foo/bar/baz";
assert_eq!(expect, &path);
path.clear();
parse_pathname_to_str(b"\xfffoo\xffb\x91ar\xffbaz\xff", &mut path);
assert!(!path.starts_with('/'));
let expect = "foo/b%91ar/baz";
assert_eq!(expect, &path);
path.clear();
}
#[test]
fn header_parse_errors() {
let mut data: &[u8];
data = &[]; assert!(LhaHeader::read(&mut data).unwrap().is_none());
data = &[0]; assert!(LhaHeader::read(&mut data).unwrap().is_none());
data = &[0, 0xff]; assert!(LhaHeader::read(&mut data).unwrap().is_none());
let test_error = |mut data: &[u8], expect| {
let err = LhaHeader::read(&mut data).unwrap_err();
assert!(matches!(err, LhaError::HeaderParse(ref e) if e == &expect),
"{:?} != {:?}", err, expect);
assert!(err.to_string().starts_with("while parsing LHA header: "));
};
let test_error_eof = |mut data: &[u8]| {
let err = LhaHeader::read(&mut data).unwrap_err();
assert!(matches!(err, LhaError::Io(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof),
"{:?} != EOF", err);
};
test_error(b"\x01\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x04", LhaHeaderError::UnknownLevel);
test_error(b"\x08\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x03\0\0M\0\0\0\0\0\0\0\0",
LhaHeaderError::Level3Signature);
test_error(b"\xff\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x01\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\xff\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x02\0\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\x04\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x03\0\0M\xff\0\0\0\x01\0\0\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\x04\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x03\0\0M\xff\0\0\0\x02\0\0\0\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\x04\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x03\0\0M\xff\0\0\0\x03\0\0\0\0\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\x04\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x03\0\0M\xff\0\0\0\x04\0\0\0\0\0\0\0",
LhaHeaderError::ExtendedHeaderSize);
test_error(b"\x15\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x00\0\0\0",
LhaHeaderError::SizeMismatch);
test_error(b"\x18\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x01\0\0\0M\0\0",
LhaHeaderError::SizeMismatch);
test_error(b"\x19\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\0\0",
LhaHeaderError::LongSizeMismatch);
test_error(b"\x1C\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\0\0",
LhaHeaderError::LongSizeMismatch);
test_error(b"\x22\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0",
LhaHeaderError::LongSizeMismatch);
test_error_eof(b"\x21\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0");
data = b"\x20\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0";
LhaHeader::read(&mut data).unwrap();
test_error(b"\x1F\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0",
LhaHeaderError::LongSizeMismatch);
data = b"\x1E\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0";
LhaHeader::read(&mut data).unwrap();
test_error(b"\x1D\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x03\0\0\x03\0\0\0\0",
LhaHeaderError::LongSizeMismatch);
test_error(b"\x16\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x00\0\0\0",
LhaHeaderError::WrappingSumMismatch);
test_error(b"\x19\xCF-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x01\0\0\0M\x03\0",
LhaHeaderError::SkipSizeMismatch);
test_error(b"\x24\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x05\0\0\0\0\x05\0\0\0\0\0\0",
LhaHeaderError::CommonHeader);
test_error(b"\x1F\0-lh0-\0\0\0\0\0\0\0\0\0\0\0\0\x20\x02\0\0M\x05\0\0\0\0\0\0",
LhaHeaderError::Crc16Mismatch);
}
}