///|
/// Caller-owned output slot. `code` writes at `data[start]` and advances `start`.
/// `size` is the number of writable bytes (not a hint).
pub(all) struct OutputBuf {
data : Array[Byte]
mut start : Int
}
///|
pub fn OutputBuf::new(size? : Int = 0) -> OutputBuf {
let n = if size < 0 { 0 } else { size }
{ data: Array::make(n, b'\x00'), start: 0, }
}
///|
/// Writable bytes left (`data.length() - start`). Out-of-range `start` is 0.
pub fn OutputBuf::remaining(self : OutputBuf) -> Int {
if self.start < 0 || self.start > self.data.length() {
0
} else {
self.data.length() - self.start
}
}
///|
/// Bytes already written, `data[0:start]`.
pub fn OutputBuf::written(self : OutputBuf) -> Bytes {
if self.start <= 0 {
return b""
}
let n = if self.start > self.data.length() {
self.data.length()
} else {
self.start
}
Bytes::from_array(self.data[:n])
}
///|
fn check_output(output : OutputBuf) -> Unit raise LzmaError {
if output.start < 0 || output.start > output.data.length() {
raise BufferTooSmall
}
}
///|
pub(all) struct CodeResult {
consumed : Int
produced : Int
status : Status
} derive(Debug, Eq)
///|
priv struct Session {
input : Array[Byte]
pending : Array[Byte]
stream_decoder : @lzma2.StreamDecoder?
xz_decoder : @xz.StreamDecoder?
mut pending_pos : Int
mut finished : Bool
mut complete : Bool
mut total_in : UInt64
mut total_out : UInt64
}
///|
struct Encoder {
options : EncodeOptions
session : Session
}
///|
struct Decoder {
options : DecodeOptions
session : Session
}
///|
fn Session::fresh(
stream_decoder? : @lzma2.StreamDecoder? = None,
xz_decoder? : @xz.StreamDecoder? = None,
) -> Session {
{
input: [],
pending: [],
stream_decoder,
xz_decoder,
pending_pos: 0,
finished: false,
complete: false,
total_in: 0UL,
total_out: 0UL,
}
}
///|
fn Session::clear(self : Session) -> Unit {
self.input.clear()
self.pending.clear()
self.pending_pos = 0
self.finished = false
self.complete = false
self.total_in = 0UL
self.total_out = 0UL
match self.stream_decoder {
None => ()
Some(dec) => dec.reset()
}
match self.xz_decoder {
None => ()
Some(dec) => dec.reset()
}
}
///|
fn add_count(total : UInt64, n : Int) -> UInt64 {
total + n.reinterpret_as_uint().to_uint64()
}
///|
fn fill_pending(pending : Array[Byte], src : Bytes) -> Unit {
pending.clear()
for b in src[:] {
pending.push(b)
}
}
///|
fn append_pending(pending : Array[Byte], src : Bytes) -> Unit {
for b in src[:] {
pending.push(b)
}
}
///|
fn drain_pending(
pending : Array[Byte],
pending_pos : Int,
output : OutputBuf,
) -> (Int, Int) {
let mut pos = pending_pos
let mut produced = 0
while pos < pending.length() && output.start < output.data.length() {
output.data[output.start] = pending[pos]
output.start = output.start + 1
pos = pos + 1
produced = produced + 1
}
(produced, pos)
}
///|
fn pending_status(
done : Bool,
pending_pos : Int,
pending_len : Int,
output : OutputBuf,
) -> Status raise LzmaError {
if done == false {
return NeedInput
}
if pending_pos >= pending_len {
return StreamEnd
}
if output.remaining() == 0 {
if output.data.length() == 0 {
raise BufferTooSmall
}
return NeedOutput
}
StreamEnd
}
///|
fn pending_rest(pending : Array[Byte], pos : Int) -> Bytes {
if pos < 0 || pos >= pending.length() {
b""
} else {
Bytes::from_array(pending[pos:])
}
}
///|
fn reject_unsupported_action(action : Action) -> Unit raise LzmaError {
match action {
SyncFlush | FullFlush =>
raise InvalidConfiguration("SyncFlush/FullFlush are not supported")
Run | Finish => ()
}
}
///|
fn reject_input_after_finish(
finished : Bool,
input : BytesView,
) -> Unit raise LzmaError {
if finished && input.length() > 0 {
raise InvalidConfiguration("cannot add input after Finish")
}
}
///|
fn Session::code(
self : Session,
input : BytesView,
output : OutputBuf,
action : Action,
produce : (Bytes) -> Bytes raise LzmaError,
kind : String,
run_can_finish? : Bool = false,
) -> CodeResult raise LzmaError {
check_output(output)
reject_unsupported_action(action)
if self.finished && self.pending_pos >= self.pending.length() {
raise InvalidConfiguration("\{kind} already finished")
}
reject_input_after_finish(self.finished, input)
// Empty output cannot receive the Finish payload. Fail before consuming
// this call's input so the caller can retry with the same bytes.
if action is Finish && self.complete == false && output.data.length() == 0 {
raise BufferTooSmall
}
for b in input {
self.input.push(b)
}
let consumed = input.length()
self.total_in = add_count(self.total_in, consumed)
match self.stream_decoder {
Some(decoder) => {
let (produced_bytes, done0) = decoder.feed(input) catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("lzma2 feature")
}
let mut done = done0
append_pending(self.pending, produced_bytes)
if action is Finish && done == false {
let _ = decoder.finish() catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("lzma2 feature")
}
done = true
}
self.complete = done
self.finished = done
let (produced, new_pos) = drain_pending(
self.pending,
self.pending_pos,
output,
)
self.pending_pos = new_pos
self.total_out = add_count(self.total_out, produced)
let status = pending_status(
self.complete,
self.pending_pos,
self.pending.length(),
output,
)
return { consumed, produced, status, }
}
None => ()
}
match self.xz_decoder {
Some(decoder) => {
let (produced_bytes, done0) = decoder.feed(input) catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("xz feature")
}
let mut done = done0
append_pending(self.pending, produced_bytes)
if action is Finish && done == false {
let _ = decoder.finish() catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("xz feature")
}
done = true
}
self.complete = done
self.finished = done
let (produced, new_pos) = drain_pending(
self.pending,
self.pending_pos,
output,
)
self.pending_pos = new_pos
self.total_out = add_count(self.total_out, produced)
let status = pending_status(
self.complete,
self.pending_pos,
self.pending.length(),
output,
)
return { consumed, produced, status, }
}
None => ()
}
if action is Run &&
run_can_finish &&
self.complete == false &&
self.input.length() > 0 {
let src = Bytes::from_array(self.input[:])
let mut run_complete = true
let out = produce(src) catch {
UnexpectedEof => {
run_complete = false
b""
}
err => raise err
}
if run_complete {
fill_pending(self.pending, out)
self.pending_pos = 0
self.complete = true
self.finished = true
}
}
if action is Finish && self.complete == false {
let src = Bytes::from_array(self.input[:])
fill_pending(self.pending, produce(src))
self.pending_pos = 0
self.complete = true
self.finished = true
}
let (produced, new_pos) = drain_pending(
self.pending,
self.pending_pos,
output,
)
self.pending_pos = new_pos
self.total_out = add_count(self.total_out, produced)
let status = pending_status(
self.complete,
self.pending_pos,
self.pending.length(),
output,
)
{ consumed, produced, status, }
}
///|
fn Session::write(
self : Session,
input : BytesView,
kind : String,
) -> Unit raise LzmaError {
if self.finished {
raise InvalidConfiguration("\{kind} already finished")
}
for b in input {
self.input.push(b)
}
self.total_in = add_count(self.total_in, input.length())
}
///|
fn Session::finish_now(
self : Session,
produce : (Bytes) -> Bytes raise LzmaError,
kind : String,
) -> Bytes raise LzmaError {
if self.complete {
if self.pending_pos >= self.pending.length() {
raise InvalidConfiguration("\{kind} already finished")
}
let rest = pending_rest(self.pending, self.pending_pos)
self.pending_pos = self.pending.length()
self.finished = true
self.total_out = add_count(self.total_out, rest.length())
return rest
}
let src = Bytes::from_array(self.input[:])
let out = produce(src)
self.finished = true
self.complete = true
self.total_out = out.length().reinterpret_as_uint().to_uint64()
out
}
///|
pub fn Encoder::new(
options? : EncodeOptions = EncodeOptions::default(),
) -> Encoder raise LzmaError {
options.validate()
{ options, session: Session::fresh(), }
}
///|
pub fn Decoder::new(
options? : DecodeOptions = DecodeOptions::default(),
) -> Decoder raise LzmaError {
options.validate()
let stream_decoder = match options.format {
Lzma2 => {
let limit = effective_memlimit(options.memlimit)
Some(
@lzma2.StreamDecoder::new(
limit,
dict_size=dict_cap_from_memlimit(limit),
) catch {
@coder.Eof => raise UnexpectedEof
@coder.Data => raise DataError
@coder.Limit => raise MemoryLimit
@coder.Unsupported => raise UnsupportedFeature("lzma2 feature")
},
)
}
Auto | Lzma | Xz => None
}
let xz_decoder : @xz.StreamDecoder? = if options.format == Format::Xz &&
options.concatenated == false &&
options.filters.length() == 0 {
Some(
@xz.StreamDecoder::new(
effective_memlimit(options.memlimit),
options.ignore_check,
),
)
} else {
None
}
{ options, session: Session::fresh(stream_decoder~, xz_decoder~), }
}
///|
pub fn Encoder::total_in(self : Encoder) -> UInt64 {
self.session.total_in
}
///|
pub fn Encoder::total_out(self : Encoder) -> UInt64 {
self.session.total_out
}
///|
pub fn Decoder::total_in(self : Decoder) -> UInt64 {
self.session.total_in
}
///|
pub fn Decoder::total_out(self : Decoder) -> UInt64 {
self.session.total_out
}
///|
pub fn Encoder::reset(self : Encoder) -> Unit raise LzmaError {
self.options.validate()
self.session.clear()
}
///|
pub fn Decoder::reset(self : Decoder) -> Unit raise LzmaError {
self.options.validate()
self.session.clear()
}
///|
pub fn Encoder::code(
self : Encoder,
input : BytesView,
output : OutputBuf,
action : Action,
) -> CodeResult raise LzmaError {
let options = self.options
self.session.code(
input,
output,
action,
(src : Bytes) => encode(src[:], options~),
"encoder",
)
}
///|
pub fn Decoder::code(
self : Decoder,
input : BytesView,
output : OutputBuf,
action : Action,
) -> CodeResult raise LzmaError {
let options = self.options
self.session.code(
input,
output,
action,
(src : Bytes) => decode(src[:], options~),
"decoder",
run_can_finish=true,
)
}
///|
pub fn Encoder::write(
self : Encoder,
input : BytesView,
) -> Unit raise LzmaError {
self.session.write(input, "encoder")
}
///|
pub fn Encoder::finish(self : Encoder) -> Bytes raise LzmaError {
let options = self.options
self.session.finish_now((src : Bytes) => encode(src[:], options~), "encoder")
}
///|
pub fn Decoder::write(
self : Decoder,
input : BytesView,
) -> Unit raise LzmaError {
self.session.write(input, "decoder")
}
///|
pub fn Decoder::finish(self : Decoder) -> Bytes raise LzmaError {
let options = self.options
self.session.finish_now((src : Bytes) => decode(src[:], options~), "decoder")
}