Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ZlibError type and move initialize into startproc #93

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions src/compression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,6 @@
# Methods
# -------

function TranscodingStreams.initialize(codec::CompressorCodec)
code = deflate_init!(codec.zstream, codec.level, codec.windowbits)
if code != Z_OK
zerror(codec.zstream, code)
end
return
end

function TranscodingStreams.finalize(codec::CompressorCodec)
zstream = codec.zstream
if zstream.state != C_NULL
Expand All @@ -174,33 +166,55 @@
return
end

function TranscodingStreams.startproc(codec::CompressorCodec, state::Symbol, error::Error)
code = deflate_reset!(codec.zstream)
if code == Z_OK
return :ok
function TranscodingStreams.startproc(codec::CompressorCodec, state::Symbol, error_ref::Error)
if codec.zstream.state == C_NULL
code = deflate_init!(codec.zstream, codec.level, codec.windowbits)
# errors in deflate_init! do not require clean up, so just throw
if code == Z_OK
return :ok
elseif code == Z_MEM_ERROR
throw(OutOfMemoryError())
elseif code == Z_STREAM_ERROR
error("Z_STREAM_ERROR: invalid parameter, this should be caught in the codec constructor")
elseif code == Z_VERSION_ERROR
error("Z_VERSION_ERROR: zlib library version is incompatible")

Check warning on line 180 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L175-L180

Added lines #L175 - L180 were not covered by tests
else
error("unexpected libz error code: $(code)")

Check warning on line 182 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L182

Added line #L182 was not covered by tests
end
else
error[] = ErrorException(zlib_error_message(codec.zstream, code))
return :error
code = deflate_reset!(codec.zstream)
# errors in deflate_reset! do not require clean up, so just throw
if code == Z_OK
return :ok
elseif code == Z_STREAM_ERROR
error("Z_STREAM_ERROR: the source stream state was inconsistent")

Check warning on line 190 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L189-L190

Added lines #L189 - L190 were not covered by tests
else
error("unexpected libz error code: $(code)")

Check warning on line 192 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L192

Added line #L192 was not covered by tests
end
end
end

function TranscodingStreams.process(codec::CompressorCodec, input::Memory, output::Memory, error::Error)
function TranscodingStreams.process(codec::CompressorCodec, input::Memory, output::Memory, error_ref::Error)
zstream = codec.zstream
if zstream.state == C_NULL
error("startproc must be called before process")

Check warning on line 200 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L200

Added line #L200 was not covered by tests
end
zstream.next_in = input.ptr
avail_in = min(input.size, typemax(UInt32))
zstream.avail_in = avail_in
zstream.next_out = output.ptr
avail_out = min(output.size, typemax(UInt32))
zstream.avail_out = avail_out
code = deflate!(zstream, zstream.avail_in > 0 ? Z_NO_FLUSH : Z_FINISH)
@assert code != Z_STREAM_ERROR # state not clobbered
Δin = Int(avail_in - zstream.avail_in)
Δout = Int(avail_out - zstream.avail_out)
if code == Z_OK
return Δin, Δout, :ok
elseif code == Z_STREAM_END
return Δin, Δout, :end
else
error[] = ErrorException(zlib_error_message(zstream, code))
error_ref[] = ErrorException(zlib_error_message(zstream, code))

Check warning on line 217 in src/compression.jl

View check run for this annotation

Codecov / codecov/patch

src/compression.jl#L217

Added line #L217 was not covered by tests
return Δin, Δout, :error
end
end
54 changes: 38 additions & 16 deletions src/decompression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,6 @@
# Methods
# -------

function TranscodingStreams.initialize(codec::DecompressorCodec)
code = inflate_init!(codec.zstream, codec.windowbits)
if code != Z_OK
zerror(codec.zstream, code)
end
return
end

function TranscodingStreams.finalize(codec::DecompressorCodec)
zstream = codec.zstream
if zstream.state != C_NULL
Expand All @@ -162,18 +154,42 @@
return
end

function TranscodingStreams.startproc(codec::DecompressorCodec, ::Symbol, error::Error)
code = inflate_reset!(codec.zstream)
if code == Z_OK
return :ok
function TranscodingStreams.startproc(codec::DecompressorCodec, ::Symbol, error_ref::Error)
# indicate that no input data is being provided for future zlib compat
codec.zstream.next_in = C_NULL
codec.zstream.avail_in = 0
if codec.zstream.state == C_NULL
code = inflate_init!(codec.zstream, codec.windowbits)
# errors in inflate_init! do not require clean up, so just throw
if code == Z_OK
return :ok
elseif code == Z_MEM_ERROR
throw(OutOfMemoryError())
elseif code == Z_STREAM_ERROR
error("Z_STREAM_ERROR: invalid parameter, this should be caught in the codec constructor")
elseif code == Z_VERSION_ERROR
error("Z_VERSION_ERROR: zlib library version is incompatible")

Check warning on line 171 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L166-L171

Added lines #L166 - L171 were not covered by tests
else
error("unexpected libz error code: $(code)")

Check warning on line 173 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L173

Added line #L173 was not covered by tests
end
else
error[] = ErrorException(zlib_error_message(codec.zstream, code))
return :error
code = inflate_reset!(codec.zstream)
# errors in deflate_reset! do not require clean up, so just throw
if code == Z_OK
return :ok
elseif code == Z_STREAM_ERROR
error("Z_STREAM_ERROR: the source stream state was inconsistent")

Check warning on line 181 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L180-L181

Added lines #L180 - L181 were not covered by tests
else
error("unexpected libz error code: $(code)")

Check warning on line 183 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L183

Added line #L183 was not covered by tests
end
end
end

function TranscodingStreams.process(codec::DecompressorCodec, input::Memory, output::Memory, error::Error)
function TranscodingStreams.process(codec::DecompressorCodec, input::Memory, output::Memory, error_ref::Error)
zstream = codec.zstream
if zstream.state == C_NULL
error("startproc must be called before process")

Check warning on line 191 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L191

Added line #L191 was not covered by tests
end
zstream.next_in = input.ptr

avail_in = min(input.size, typemax(UInt32))
Expand All @@ -182,14 +198,20 @@
avail_out = min(output.size, typemax(UInt32))
zstream.avail_out = avail_out
code = inflate!(zstream, Z_NO_FLUSH)
@assert code != Z_STREAM_ERROR # state not clobbered
Δin = Int(avail_in - zstream.avail_in)
Δout = Int(avail_out - zstream.avail_out)
if code == Z_OK
return Δin, Δout, :ok
elseif code == Z_STREAM_END
return Δin, Δout, :end
elseif code == Z_MEM_ERROR
throw(OutOfMemoryError())

Check warning on line 209 in src/decompression.jl

View check run for this annotation

Codecov / codecov/patch

src/decompression.jl#L209

Added line #L209 was not covered by tests
elseif code == Z_BUF_ERROR && iszero(input.size)
error_ref[] = ZlibError("the compressed stream may be truncated")
return Δin, Δout, :error
else
error[] = ErrorException(zlib_error_message(zstream, code))
error_ref[] = ZlibError(zlib_error_message(zstream, code))
return Δin, Δout, :error
end
end
38 changes: 31 additions & 7 deletions src/libz.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@

const Z_DEFAULT_COMPRESSION = Cint(-1)

const Z_OK = Cint(0)
const Z_STREAM_END = Cint(1)
const Z_BUF_ERROR = Cint(-5)
const Z_OK = Cint(0)
const Z_STREAM_END = Cint(1)
const Z_NEED_DICT = Cint(2)
const Z_ERRNO = Cint(-1)
const Z_STREAM_ERROR = Cint(-2)
const Z_DATA_ERROR = Cint(-3)
const Z_MEM_ERROR = Cint(-4)
const Z_BUF_ERROR = Cint(-5)
const Z_VERSION_ERROR = Cint(-6)
# Return codes for the compression/decompression functions. Negative values
# are errors, positive values are used for special but normal events.

const Z_NO_FLUSH = Cint(0)
const Z_SYNC_FLUSH = Cint(2)
Expand All @@ -59,7 +67,9 @@
return unsafe_string(ccall((:zlibVersion, libz), Ptr{UInt8}, ()))
end

const zlib_version = version()
# This is the version of zlib used to make this wrapper.
# The `_init!` functions will return an error if the library is not compatible.
const zlib_version = "1.3.1"

function deflate_init!(zstream::ZStream, level::Integer, windowbits::Integer)
return ccall((:deflateInit2_, libz), Cint, (Ref{ZStream}, Cint, Cint, Cint, Cint, Cint, Cstring, Cint), zstream, level, Z_DEFLATED, windowbits, #=default memlevel=#8, #=default strategy=#0, zlib_version, sizeof(ZStream))
Expand Down Expand Up @@ -93,14 +103,28 @@
return ccall((:inflate, libz), Cint, (Ref{ZStream}, Cint), zstream, flush)
end

# Error
# -----

struct ZlibError <: Exception
msg::String
end

function Base.showerror(io::IO, err::ZlibError)
print(io, "ZlibError: ")
print(io, err.msg)
nothing
end


function zerror(zstream::ZStream, code::Integer)
return throw(ErrorException(zlib_error_message(zstream, code)))
throw(ZlibError(zlib_error_message(zstream, code)))

Check warning on line 121 in src/libz.jl

View check run for this annotation

Codecov / codecov/patch

src/libz.jl#L121

Added line #L121 was not covered by tests
end

function zlib_error_message(zstream::ZStream, code::Integer)
if zstream.msg == C_NULL
return "zlib error: <no message> (code: $(code))"
return "<no message> (code: $(code))"

Check warning on line 126 in src/libz.jl

View check run for this annotation

Codecov / codecov/patch

src/libz.jl#L126

Added line #L126 was not covered by tests
else
return "zlib error: $(unsafe_string(zstream.msg)) (code: $(code))"
return "$(unsafe_string(zstream.msg)) (code: $(code))"
end
end
36 changes: 33 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CodecZlib
using CodecZlib: ZlibError
using Test
using Aqua: Aqua
using TranscodingStreams:
Expand Down Expand Up @@ -56,7 +57,7 @@ const testdir = @__DIR__
gzip_data_corrupted[1] = 0x00 # corrupt header
file = IOBuffer(gzip_data_corrupted)
stream = GzipDecompressorStream(file)
@test_throws ErrorException read(stream)
@test_throws ZlibError read(stream)
@test_throws ArgumentError read(stream)
@test !isopen(stream)
@test isopen(file)
Expand Down Expand Up @@ -160,7 +161,7 @@ end
close(stream)

stream = TranscodingStream(GzipDecompressor(gziponly=true), IOBuffer(zlib_data))
@test_throws Exception read(stream)
@test_throws ZlibError read(stream)
close(stream)

file = IOBuffer(b"foo")
Expand Down Expand Up @@ -251,7 +252,7 @@ end

@testset "panic" begin
stream = TranscodingStream(GzipDecompressor(), IOBuffer("some invalid data"))
@test_throws ErrorException read(stream)
@test_throws ZlibError read(stream)
@test_throws ArgumentError eof(stream)
end

Expand Down Expand Up @@ -304,3 +305,32 @@ end
@test_throws ArgumentError TranscodingStreams.stats(stream)
end
end

@testset "unexpected end of stream errors" begin
tests = [
(ZlibCompressor, ZlibDecompressor),
(DeflateCompressor, DeflateDecompressor),
(GzipCompressor, GzipDecompressor),
]
@testset "$(encoder)" for (encoder, decoder) in tests
local uncompressed = rand(UInt8, 1000)
local compressed = transcode(encoder, uncompressed)
for i in 0:length(compressed)-1
@test_throws ZlibError("the compressed stream may be truncated") transcode(decoder, compressed[1:i])
end
@test transcode(decoder, compressed) == uncompressed
# compressing empty vector should still work
@test transcode(decoder, transcode(encoder, UInt8[])) == UInt8[]
end
end
@testset "data errors" begin
@test_throws ZlibError transcode(ZlibDecompressor, zeros(UInt8, 10))
local uncompressed = rand(UInt8, 1000)
local compressed = transcode(ZlibCompressor, uncompressed)
compressed[70] ⊻= 0x01
@test_throws ZlibError transcode(ZlibDecompressor, compressed)
end
@testset "error printing" begin
@test sprint(Base.showerror, ZlibError("test error message")) ==
"ZlibError: test error message"
end
Loading