///|
fn append_bytes(buf : Array[Byte], view : BytesView) -> Unit {
for b in view {
buf.push(b)
}
}
///|
fn view_to_bytes(view : BytesView) -> Bytes {
let a = Array(capacity=view.length())
append_bytes(a, view)
Bytes::from_array(a[:])
}
///|
fn apply_encode_filters(
input : BytesView,
filters : Array[FilterSpec],
) -> Bytes raise LzmaError {
let mut cur = view_to_bytes(input)
for f in filters {
match f {
Lzma1(_) | Lzma2(_) => raise InvalidConfiguration("codec filter in chain")
Delta(distance~) =>
cur = @filters.delta_encode(cur[:], distance) catch {
_ => raise InvalidConfiguration("delta distance")
}
BcjX86(start_offset~) =>
cur = @filters.bcj_x86(cur[:], start_offset, true)
}
}
cur
}
///|
fn apply_decode_filters(
input : Bytes,
filters : Array[FilterSpec],
) -> Bytes raise LzmaError {
let mut cur = input
let mut i = filters.length() - 1
while i >= 0 {
match filters[i] {
Lzma1(_) | Lzma2(_) => raise InvalidConfiguration("codec filter in chain")
Delta(distance~) =>
cur = @filters.delta_decode(cur[:], distance) catch {
_ => raise InvalidConfiguration("delta distance")
}
BcjX86(start_offset~) =>
cur = @filters.bcj_x86(cur[:], start_offset, false)
}
i = i - 1
}
cur
}
///|
fn encode_format(
input : BytesView,
options : EncodeOptions,
props : LzmaProps,
) -> Bytes raise LzmaError {
match options.format {
Auto =>
raise InvalidConfiguration("Format::Auto cannot be used for encoding")
Lzma =>
@lzma.encode_alone(input, props.lc, props.lp, props.pb, props.dict_size)
Lzma2 =>
@lzma2.encode_lzma2(input, props.lc, props.lp, props.pb, props.dict_size)
Xz => {
let check_id = match options.check {
None => 0
Crc32 => 1
Crc64 => 4
Sha256 => 10
}
@xz.encode_xz(
input,
props.lc,
props.lp,
props.pb,
props.dict_size,
check_id~,
) catch {
@coder.Unsupported => raise UnsupportedFeature("xz check")
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
}
}
}
}
///|
fn encode_body(
input : BytesView,
options : EncodeOptions,
) -> Bytes raise LzmaError {
let props = options.resolved_props()
if options.filters.length() == 0 {
encode_format(input, options, props)
} else {
let filtered = apply_encode_filters(input, options.filters)
encode_format(filtered[:], options, props)
}
}
///|
fn dict_cap_from_memlimit(limit : UInt64) -> UInt {
let maxd = MAX_DICT_SIZE.to_uint64()
let n = if limit > maxd { maxd } else { limit }
if n == 0UL {
1U
} else {
n.reinterpret_as_int64().to_int().reinterpret_as_uint()
}
}
///|
fn looks_like_xz(input : BytesView) -> Bool {
input.length() >= 6 &&
input[0] == b'\xFD' &&
input[1] == b'\x37' &&
input[2] == b'\x7A' &&
input[3] == b'\x58' &&
input[4] == b'\x5A' &&
input[5] == b'\x00'
}
///|
fn lzma_props_byte_ok(b : Byte) -> Bool {
try {
unpack_props(b) |> ignore
true
} catch {
_ => false
}
}
///|
fn looks_like_lzma(input : BytesView) -> Bool {
input.length() >= 13 && lzma_props_byte_ok(input[0])
}
///|
fn xz_magic_prefix(input : BytesView) -> Bool {
let magic = b"\xFD\x37\x7A\x58\x5A\x00"
if input.length() == 0 || input.length() >= 6 {
return false
}
let mut i = 0
while i < input.length() {
if input[i] != magic[i] {
return false
}
i = i + 1
}
true
}
///|
fn looks_like_truncated_lzma(input : BytesView) -> Bool {
input.length() > 0 && input.length() < 13 && lzma_props_byte_ok(input[0])
}
///|
fn decode_body(
input : BytesView,
options : DecodeOptions,
) -> Bytes raise LzmaError {
options.validate()
let limit = effective_memlimit(options.memlimit)
let fmt = match options.format {
Auto =>
if looks_like_xz(input) {
Format::Xz
} else if looks_like_lzma(input) {
Format::Lzma
} else if input.length() == 0 ||
xz_magic_prefix(input) ||
looks_like_truncated_lzma(input) {
raise UnexpectedEof
} else {
raise FormatError
}
Xz => {
if input.length() < 6 {
raise UnexpectedEof
}
if looks_like_xz(input) == false {
raise FormatError
}
Format::Xz
}
Lzma => {
if looks_like_xz(input) {
raise FormatError
}
if input.length() >= 13 && looks_like_lzma(input) == false {
raise FormatError
}
Format::Lzma
}
Lzma2 => {
if looks_like_xz(input) {
raise FormatError
}
Format::Lzma2
}
}
let raw = try {
match fmt {
Auto | Lzma => @lzma.decode_alone(input, limit)
Lzma2 =>
@lzma2.decode_lzma2(
input,
limit,
dict_size=dict_cap_from_memlimit(limit),
)
Xz =>
@xz.decode_xz(
input,
limit,
options.ignore_check,
concatenated=options.concatenated,
)
}
} catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("lzma2/xz feature")
}
apply_decode_filters(raw, options.filters)
}
///|
pub fn encode(
input : BytesView,
options? : EncodeOptions = EncodeOptions::default(),
) -> Bytes raise LzmaError {
options.validate()
encode_body(input, options)
}
///|
pub fn decode(
input : BytesView,
options? : DecodeOptions = DecodeOptions::default(),
) -> Bytes raise LzmaError {
decode_body(input, options)
}