///|
/// Input format selector for the streaming reader.
pub(all) enum CpioFormat {
Newc
Odc
BinaryOld
BinaryNew(BinaryByteOrder)
} derive(Eq, Debug)
///|
/// Format-specific metadata delivered for each streamed entry.
pub(all) enum StreamHeader {
Newc(Header)
Odc(OdcHeader)
Binary(BinaryHeader)
} derive(Eq, Debug)
///|
/// One zero-copy entry delivered by `stream_entries`.
///
/// `name_bytes` and `data` are views into the input buffer and remain valid
/// only while that buffer is alive.
pub(all) struct StreamEntry {
name : String
name_bytes : BytesView
header : StreamHeader
data : BytesView
} derive(Eq, Debug)
///|
fn stream_newc(
input : Bytes,
on_entry : (StreamEntry) -> Unit,
) -> Result[Unit, CpioError] {
let mut format = NewcFormat::Newc
let mut has_format = false
let mut offset = 0
while true {
let header_end = offset + NEWC_HEADER_SIZE
if header_end > input.length() {
return Err(TruncatedHeader(offset))
}
let header = match parse_header(input[offset:header_end], offset) {
Ok(header) => header
Err(error) => return Err(error)
}
if has_format {
if header.format != format {
return Err(MixedFormats(offset))
}
} else {
format = header.format
has_format = true
}
let name_start = header_end
let remaining = (input.length() - name_start).to_uint64()
if header.name_size == 0UL || header.name_size > remaining {
return Err(InvalidNameSize(offset))
}
let name_size = header.name_size.to_int()
let name_end = name_start + name_size
if input[name_end - 1] != b'\x00' {
return Err(MissingNameTerminator(name_start))
}
let name_view = input[name_start:name_end - 1]
for i = 0; i < name_view.length(); i = i + 1 {
if name_view[i] == b'\x00' {
return Err(EmbeddedNul(name_start + i))
}
}
let name = @utf8.decode(name_view) catch {
_ => return Err(InvalidUtf8Name(name_start))
}
let data_start = name_end + newc_name_padding(name_size)
if data_start > input.length() {
return Err(TruncatedName(name_end))
}
if name == "TRAILER!!!" {
if header.file_size != 0UL {
return Err(InvalidTrailer(offset))
}
if header.format == NewcCrc && header.check != 0UL {
return Err(ChecksumMismatch(offset, header.check, 0UL))
}
return Ok(())
}
let data_remaining = (input.length() - data_start).to_uint64()
if header.file_size > data_remaining {
return Err(TruncatedData(data_start))
}
let file_size = header.file_size.to_int()
let data_end = data_start + file_size
let next_offset = data_end + padding_for(file_size)
if next_offset > input.length() {
return Err(TruncatedData(data_end))
}
let data = input[data_start:data_end]
if header.format == NewcCrc {
let actual = newc_checksum(data)
if header.check != actual {
return Err(ChecksumMismatch(data_start, header.check, actual))
}
}
on_entry({
name,
name_bytes: name_view,
header: StreamHeader::Newc(header),
data,
})
offset = next_offset
} nobreak {
abort("unreachable: stream_newc always returns")
}
}
///|
fn stream_odc(
input : Bytes,
on_entry : (StreamEntry) -> Unit,
) -> Result[Unit, CpioError] {
let mut offset = 0
while true {
let header_end = offset + ODC_HEADER_SIZE
if header_end > input.length() {
return Err(TruncatedHeader(offset))
}
let header = match parse_odc_header(input[offset:header_end], offset) {
Ok(header) => header
Err(error) => return Err(error)
}
let name_start = header_end
let remaining = (input.length() - name_start).to_uint64()
if header.name_size == 0UL || header.name_size > remaining {
return Err(InvalidNameSize(offset))
}
let name_size = header.name_size.to_int()
let name_end = name_start + name_size
if input[name_end - 1] != b'\x00' {
return Err(MissingNameTerminator(name_start))
}
let name_view = input[name_start:name_end - 1]
for i = 0; i < name_view.length(); i = i + 1 {
if name_view[i] == b'\x00' {
return Err(EmbeddedNul(name_start + i))
}
}
let name = @utf8.decode(name_view) catch {
_ => return Err(InvalidUtf8Name(name_start))
}
if name == "TRAILER!!!" {
if header.file_size != 0UL {
return Err(InvalidTrailer(offset))
}
return Ok(())
}
let data_remaining = (input.length() - name_end).to_uint64()
if header.file_size > data_remaining {
return Err(TruncatedData(name_end))
}
let data_end = name_end + header.file_size.to_int()
on_entry({
name,
name_bytes: name_view,
header: StreamHeader::Odc(header),
data: input[name_end:data_end],
})
offset = data_end
} nobreak {
abort("unreachable: stream_odc always returns")
}
}
///|
fn stream_binary(
input : Bytes,
variant : BinaryVariant,
byte_order : BinaryByteOrder,
on_entry : (StreamEntry) -> Unit,
) -> Result[Unit, CpioError] {
let mut offset = 0
while true {
let header_end = offset + BINARY_HEADER_SIZE
if header_end > input.length() {
return Err(TruncatedHeader(offset))
}
let header = match
parse_binary_header(input[offset:header_end], variant, byte_order, offset) {
Ok(header) => header
Err(error) => return Err(error)
}
let name_start = header_end
let remaining = (input.length() - name_start).to_uint64()
if header.name_size == 0UL || header.name_size > remaining {
return Err(InvalidNameSize(offset))
}
let name_size = header.name_size.to_int()
let name_end = name_start + name_size
if input[name_end - 1] != b'\x00' {
return Err(MissingNameTerminator(name_start))
}
let name_view = input[name_start:name_end - 1]
for i = 0; i < name_view.length(); i = i + 1 {
if name_view[i] == b'\x00' {
return Err(EmbeddedNul(name_start + i))
}
}
let name = @utf8.decode(name_view) catch {
_ => return Err(InvalidUtf8Name(name_start))
}
let data_start = name_end + binary_padding(name_size)
if data_start > input.length() {
return Err(TruncatedName(name_end))
}
if name == "TRAILER!!!" {
if header.file_size != 0UL {
return Err(InvalidTrailer(offset))
}
return Ok(())
}
let data_remaining = (input.length() - data_start).to_uint64()
if header.file_size > data_remaining {
return Err(TruncatedData(data_start))
}
let file_size = header.file_size.to_int()
let data_end = data_start + file_size
let next_offset = data_end + binary_padding(file_size)
if next_offset > input.length() {
return Err(TruncatedData(data_end))
}
on_entry({
name,
name_bytes: name_view,
header: StreamHeader::Binary(header),
data: input[data_start:data_end],
})
offset = next_offset
} nobreak {
abort("unreachable: stream_binary always returns")
}
}
///|
/// Streams entries one at a time without constructing an archive array or
/// copying entry data.
///
/// The callback executes synchronously for each entry. Binary old/new layout
/// ambiguity is resolved by the explicit `CpioFormat`.
pub fn stream_entries(
input : Bytes,
format : CpioFormat,
on_entry : (StreamEntry) -> Unit,
) -> Result[Unit, CpioError] {
match format {
Newc => stream_newc(input, on_entry)
Odc => stream_odc(input, on_entry)
BinaryOld => stream_binary(input, Old, Little, on_entry)
BinaryNew(byte_order) => stream_binary(input, New, byte_order, on_entry)
}
}