|
| 1 | +""" |
| 2 | + LZMADecodingError(code) |
| 3 | +
|
| 4 | +Error for data that cannot be decoded. |
| 5 | +""" |
| 6 | +struct LZMADecodingError <: DecodingError |
| 7 | + code::Cint |
| 8 | +end |
| 9 | + |
| 10 | +function Base.showerror(io::IO, err::LZMADecodingError) |
| 11 | + print(io, "LZMADecodingError: ") |
| 12 | + if err.code == LZMA_DATA_ERROR |
| 13 | + print(io, "LZMA_DATA_ERROR: data is corrupt") |
| 14 | + elseif err.code == LZMA_FORMAT_ERROR |
| 15 | + print(io, "LZMA_FORMAT_ERROR: file format not recognized") |
| 16 | + elseif err.code == LZMA_OPTIONS_ERROR |
| 17 | + print(io, "LZMA_OPTIONS_ERROR: reserved bits set in headers. Data corrupt, or upgrading liblzma may help") |
| 18 | + elseif err.code == LZMA_BUF_ERROR |
| 19 | + print(io, "LZMA_BUF_ERROR: the compressed stream may be truncated or corrupt") |
| 20 | + else |
| 21 | + print(io, "unknown lzma error code: ") |
| 22 | + print(io, err.code) |
| 23 | + end |
| 24 | + nothing |
| 25 | +end |
| 26 | + |
| 27 | +""" |
| 28 | + struct XZDecodeOptions <: DecodeOptions |
| 29 | + XZDecodeOptions(; kwargs...) |
| 30 | +
|
| 31 | +xz decompression using the liblzma C library <https://tukaani.org/xz/> |
| 32 | +
|
| 33 | +Like the command line tool `xz`, decoding accepts concatenated and padded compressed data and returns the decompressed data concatenated. |
| 34 | +
|
| 35 | +# Keyword Arguments |
| 36 | +
|
| 37 | +- `codec::XZCodec=XZCodec()` |
| 38 | +""" |
| 39 | +struct XZDecodeOptions <: DecodeOptions |
| 40 | + codec::XZCodec |
| 41 | +end |
| 42 | +function XZDecodeOptions(; |
| 43 | + codec::XZCodec=XZCodec(), |
| 44 | + kwargs... |
| 45 | + ) |
| 46 | + XZDecodeOptions(codec) |
| 47 | +end |
| 48 | +can_concatenate(::XZDecodeOptions) = true |
| 49 | + |
| 50 | +function try_find_decoded_size(::XZDecodeOptions, src::AbstractVector{UInt8})::Nothing |
| 51 | + # Potentially this could be found by parsing through the index |
| 52 | + # This is complicated by potential padding and concatenated streams |
| 53 | + nothing |
| 54 | +end |
| 55 | + |
| 56 | +function try_decode!(d::XZDecodeOptions, dst::AbstractVector{UInt8}, src::AbstractVector{UInt8}; kwargs...)::MaybeSize |
| 57 | + try_resize_decode!(d, dst, src, Int64(length(dst))) |
| 58 | +end |
| 59 | + |
| 60 | +function try_resize_decode!(d::XZDecodeOptions, dst::AbstractVector{UInt8}, src::AbstractVector{UInt8}, max_size::Int64; kwargs...)::MaybeSize |
| 61 | + dst_size::Int64 = length(dst) |
| 62 | + src_size::Int64 = length(src) |
| 63 | + src_left::Int64 = src_size |
| 64 | + dst_left::Int64 = dst_size |
| 65 | + check_contiguous(dst) |
| 66 | + check_contiguous(src) |
| 67 | + if isempty(src) |
| 68 | + throw(LZMADecodingError(LZMA_BUF_ERROR)) |
| 69 | + end |
| 70 | + cconv_src = Base.cconvert(Ptr{UInt8}, src) |
| 71 | + # We start by allocating our allocator |
| 72 | + cconv_allocator = Base.cconvert(Ref{lzma_allocator}, default_allocator()) |
| 73 | + GC.@preserve cconv_allocator begin |
| 74 | + allocator_p = Base.unsafe_convert(Ref{lzma_allocator}, cconv_allocator) |
| 75 | + stream = lzma_stream() |
| 76 | + stream.allocator = allocator_p |
| 77 | + ret = @ccall liblzma.lzma_stream_decoder( |
| 78 | + stream::Ref{lzma_stream}, |
| 79 | + typemax(UInt64)::UInt64, |
| 80 | + LZMA_CONCATENATED::UInt32, |
| 81 | + )::Cint |
| 82 | + if ret == LZMA_MEM_ERROR |
| 83 | + throw(OutOfMemoryError()) |
| 84 | + elseif ret != LZMA_OK |
| 85 | + error("Unknown lzma error code: $(ret)") |
| 86 | + end |
| 87 | + try |
| 88 | + while true # Loop for resizing dst |
| 89 | + # dst may get resized, so cconvert needs to be redone on each iteration. |
| 90 | + cconv_dst = Base.cconvert(Ptr{UInt8}, dst) |
| 91 | + GC.@preserve cconv_src cconv_dst begin |
| 92 | + src_p = Base.unsafe_convert(Ptr{UInt8}, cconv_src) |
| 93 | + dst_p = Base.unsafe_convert(Ptr{UInt8}, cconv_dst) |
| 94 | + stream.avail_in = src_left |
| 95 | + stream.avail_out = dst_left |
| 96 | + stream.next_in = src_p + (src_size - src_left) |
| 97 | + stream.next_out = dst_p + (dst_size - dst_left) |
| 98 | + ret = @ccall liblzma.lzma_code( |
| 99 | + stream::Ref{lzma_stream}, |
| 100 | + LZMA_FINISH::Cint, |
| 101 | + )::Cint |
| 102 | + if ret == LZMA_OK || ret == LZMA_STREAM_END |
| 103 | + @assert stream.avail_in ≤ src_left |
| 104 | + @assert stream.avail_out ≤ dst_left |
| 105 | + src_left = stream.avail_in |
| 106 | + dst_left = stream.avail_out |
| 107 | + @assert src_left ∈ 0:src_size |
| 108 | + @assert dst_left ∈ 0:dst_size |
| 109 | + end |
| 110 | + if ret == LZMA_OK |
| 111 | + # Likely not enough output space |
| 112 | + # but also potentially the input is truncated |
| 113 | + # Unlike zlib, we can keep trying until we get LZMA_BUF_ERROR |
| 114 | + if iszero(dst_left) |
| 115 | + # Give more space and try again |
| 116 | + # This might result in returning a NOT_SIZE |
| 117 | + # when instead the actual issue is that the input is truncated. |
| 118 | + local next_size = grow_dst!(dst, max_size) |
| 119 | + if isnothing(next_size) |
| 120 | + return NOT_SIZE |
| 121 | + end |
| 122 | + dst_left += next_size - dst_size |
| 123 | + dst_size = next_size |
| 124 | + @assert dst_left > 0 |
| 125 | + end |
| 126 | + elseif ret == LZMA_STREAM_END |
| 127 | + @assert iszero(src_left) |
| 128 | + # yay done return decompressed size |
| 129 | + real_dst_size = dst_size - dst_left |
| 130 | + @assert real_dst_size ∈ 0:length(dst) |
| 131 | + return real_dst_size |
| 132 | + elseif ret == LZMA_DATA_ERROR || ret == LZMA_FORMAT_ERROR || ret == LZMA_OPTIONS_ERROR || ret == LZMA_BUF_ERROR |
| 133 | + throw(LZMADecodingError(ret)) |
| 134 | + elseif ret == LZMA_MEM_ERROR |
| 135 | + throw(OutOfMemoryError()) |
| 136 | + else |
| 137 | + error("Unknown lzma error code: $(ret)") |
| 138 | + end |
| 139 | + end |
| 140 | + end |
| 141 | + finally |
| 142 | + @ccall liblzma.lzma_end(stream::Ref{lzma_stream})::Cvoid |
| 143 | + end |
| 144 | + end |
| 145 | +end |
0 commit comments