{-# LANGUAGE CPP #-}
module Codec.Compression.Zlib.Internal (
compress,
CompressParams(..),
defaultCompressParams,
decompress,
DecompressParams(..),
defaultDecompressParams,
Stream.Format(..),
Stream.gzipFormat,
Stream.zlibFormat,
Stream.rawFormat,
Stream.gzipOrZlibFormat,
Stream.CompressionLevel(..),
Stream.defaultCompression,
Stream.noCompression,
Stream.bestSpeed,
Stream.bestCompression,
Stream.compressionLevel,
Stream.Method(..),
Stream.deflateMethod,
Stream.WindowBits(..),
Stream.defaultWindowBits,
Stream.windowBits,
Stream.MemoryLevel(..),
Stream.defaultMemoryLevel,
Stream.minMemoryLevel,
Stream.maxMemoryLevel,
Stream.memoryLevel,
Stream.CompressionStrategy(..),
Stream.defaultStrategy,
Stream.filteredStrategy,
Stream.huffmanOnlyStrategy,
decompressWithErrors,
DecompressStream(..),
DecompressError(..),
foldDecompressStream,
fromDecompressStream,
) where
import Prelude hiding (length)
import Control.Monad (when)
import Control.Exception (assert)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Internal as L
import qualified Data.ByteString.Internal as S
import qualified Codec.Compression.Zlib.Stream as Stream
import Codec.Compression.Zlib.Stream (Stream)
data CompressParams = CompressParams {
compressLevel :: !Stream.CompressionLevel,
compressMethod :: !Stream.Method,
compressWindowBits :: !Stream.WindowBits,
compressMemoryLevel :: !Stream.MemoryLevel,
compressStrategy :: !Stream.CompressionStrategy,
compressBufferSize :: !Int,
compressDictionary :: Maybe S.ByteString
}
data DecompressParams = DecompressParams {
decompressWindowBits :: !Stream.WindowBits,
decompressBufferSize :: !Int,
decompressDictionary :: Maybe S.ByteString
}
defaultCompressParams :: CompressParams
defaultCompressParams = CompressParams {
compressLevel = Stream.defaultCompression,
compressMethod = Stream.deflateMethod,
compressWindowBits = Stream.defaultWindowBits,
compressMemoryLevel = Stream.defaultMemoryLevel,
compressStrategy = Stream.defaultStrategy,
compressBufferSize = defaultCompressBufferSize,
compressDictionary = Nothing
}
defaultDecompressParams :: DecompressParams
defaultDecompressParams = DecompressParams {
decompressWindowBits = Stream.defaultWindowBits,
decompressBufferSize = defaultDecompressBufferSize,
decompressDictionary = Nothing
}
defaultCompressBufferSize, defaultDecompressBufferSize :: Int
defaultCompressBufferSize = 16 * 1024 - L.chunkOverhead
defaultDecompressBufferSize = 32 * 1024 - L.chunkOverhead
data DecompressStream = StreamEnd
| StreamChunk S.ByteString DecompressStream
| StreamError DecompressError String
data DecompressError =
TruncatedInput
| DictionaryRequired
| DataError
foldDecompressStream :: (S.ByteString -> a -> a) -> a
-> (DecompressError -> String -> a)
-> DecompressStream -> a
foldDecompressStream chunk end err = fold
where
fold StreamEnd = end
fold (StreamChunk bs stream) = chunk bs (fold stream)
fold (StreamError code msg) = err code msg
fromDecompressStream :: DecompressStream -> L.ByteString
fromDecompressStream =
foldDecompressStream L.Chunk L.Empty
(\_code msg -> error ("Codec.Compression.Zlib: " ++ msg))
compress
:: Stream.Format
-> CompressParams
-> L.ByteString
-> L.ByteString
compress format
(CompressParams compLevel method bits memLevel strategy initChunkSize mdict)
input =
L.fromChunks $ Stream.run $ do
Stream.deflateInit format compLevel method bits memLevel strategy
setDictionary mdict
case L.toChunks input of
[] -> fillBuffers 20 []
S.PS inFPtr offset length : chunks -> do
Stream.pushInputBuffer inFPtr offset length
r <- fillBuffers initChunkSize chunks
return r
where
fillBuffers :: Int
-> [S.ByteString]
-> Stream [S.ByteString]
fillBuffers outChunkSize inChunks = do
#ifdef DEBUG
Stream.consistencyCheck
#endif
inputBufferEmpty <- Stream.inputBufferEmpty
outputBufferFull <- Stream.outputBufferFull
assert (inputBufferEmpty || outputBufferFull) $ return ()
when outputBufferFull $ do
outFPtr <- Stream.unsafeLiftIO (S.mallocByteString outChunkSize)
Stream.pushOutputBuffer outFPtr 0 outChunkSize
if inputBufferEmpty
then case inChunks of
[] -> drainBuffers []
S.PS inFPtr offset length : inChunks' -> do
Stream.pushInputBuffer inFPtr offset length
drainBuffers inChunks'
else drainBuffers inChunks
drainBuffers ::
[S.ByteString]
-> Stream [S.ByteString]
drainBuffers inChunks = do
inputBufferEmpty' <- Stream.inputBufferEmpty
outputBufferFull' <- Stream.outputBufferFull
assert(not outputBufferFull'
&& (null inChunks || not inputBufferEmpty')) $ return ()
let flush = if null inChunks then Stream.Finish else Stream.NoFlush
status <- Stream.deflate flush
case status of
Stream.Ok -> do
outputBufferFull <- Stream.outputBufferFull
if outputBufferFull
then do (outFPtr, offset, length) <- Stream.popOutputBuffer
outChunks <- Stream.unsafeInterleave
(fillBuffers defaultCompressBufferSize inChunks)
return (S.PS outFPtr offset length : outChunks)
else do fillBuffers defaultCompressBufferSize inChunks
Stream.StreamEnd -> do
inputBufferEmpty <- Stream.inputBufferEmpty
assert inputBufferEmpty $ return ()
outputBufferBytesAvailable <- Stream.outputBufferBytesAvailable
if outputBufferBytesAvailable > 0
then do (outFPtr, offset, length) <- Stream.popOutputBuffer
Stream.finalise
return [S.PS outFPtr offset length]
else do Stream.finalise
return []
Stream.Error code msg -> case code of
Stream.BufferError -> fail "BufferError should be impossible!"
Stream.NeedDict _ -> fail "NeedDict is impossible!"
_ -> fail msg
setDictionary :: Maybe S.ByteString -> Stream ()
setDictionary (Just dict)
| Stream.formatSupportsDictionary format = do
status <- Stream.deflateSetDictionary dict
case status of
Stream.Ok -> return ()
Stream.Error _ msg -> fail msg
_ -> fail "error when setting deflate dictionary"
setDictionary _ = return ()
decompress
:: Stream.Format
-> DecompressParams
-> L.ByteString
-> L.ByteString
decompress format params = fromDecompressStream
. decompressWithErrors format params
decompressWithErrors
:: Stream.Format
-> DecompressParams
-> L.ByteString
-> DecompressStream
decompressWithErrors format (DecompressParams bits initChunkSize mdict) input =
Stream.run $ do
Stream.inflateInit format bits
case L.toChunks input of
[] -> fillBuffers 4 []
S.PS inFPtr offset length : chunks -> do
Stream.pushInputBuffer inFPtr offset length
fillBuffers initChunkSize chunks
where
fillBuffers :: Int
-> [S.ByteString]
-> Stream DecompressStream
fillBuffers outChunkSize inChunks = do
#ifdef DEBUG
Stream.consistencyCheck
#endif
inputBufferEmpty <- Stream.inputBufferEmpty
outputBufferFull <- Stream.outputBufferFull
assert (inputBufferEmpty || outputBufferFull) $ return ()
when outputBufferFull $ do
outFPtr <- Stream.unsafeLiftIO (S.mallocByteString outChunkSize)
Stream.pushOutputBuffer outFPtr 0 outChunkSize
if inputBufferEmpty
then case inChunks of
[] -> drainBuffers []
S.PS inFPtr offset length : inChunks' -> do
Stream.pushInputBuffer inFPtr offset length
drainBuffers inChunks'
else drainBuffers inChunks
drainBuffers ::
[S.ByteString]
-> Stream DecompressStream
drainBuffers inChunks = do
inputBufferEmpty' <- Stream.inputBufferEmpty
outputBufferFull' <- Stream.outputBufferFull
assert(not outputBufferFull'
&& (null inChunks || not inputBufferEmpty')) $ return ()
status <- Stream.inflate Stream.NoFlush
case status of
Stream.Ok -> do
outputBufferFull <- Stream.outputBufferFull
if outputBufferFull
then do (outFPtr, offset, length) <- Stream.popOutputBuffer
outChunks <- Stream.unsafeInterleave
(fillBuffers defaultDecompressBufferSize inChunks)
return $ StreamChunk (S.PS outFPtr offset length) outChunks
else do fillBuffers defaultDecompressBufferSize inChunks
Stream.StreamEnd -> inChunks `seq` finish StreamEnd
Stream.Error code msg -> case code of
Stream.BufferError -> finish (StreamError TruncatedInput msg')
where msg' = "premature end of compressed stream"
Stream.NeedDict adler -> do
err <- setDictionary adler mdict
case err of
Just streamErr -> finish streamErr
Nothing -> drainBuffers inChunks
Stream.DataError -> finish (StreamError DataError msg)
_ -> fail msg
finish end = do
outputBufferBytesAvailable <- Stream.outputBufferBytesAvailable
if outputBufferBytesAvailable > 0
then do (outFPtr, offset, length) <- Stream.popOutputBuffer
Stream.finalise
return (StreamChunk (S.PS outFPtr offset length) end)
else do Stream.finalise
return end
setDictionary :: Stream.DictionaryHash -> Maybe S.ByteString
-> Stream (Maybe DecompressStream)
setDictionary _adler Nothing =
return $ Just (StreamError DictionaryRequired "custom dictionary needed")
setDictionary _adler (Just dict) = do
status <- Stream.inflateSetDictionary dict
case status of
Stream.Ok -> return Nothing
Stream.Error Stream.StreamError _ ->
return $ Just (StreamError DictionaryRequired "provided dictionary not valid")
Stream.Error Stream.DataError _ ->
return $ Just (StreamError DictionaryRequired "given dictionary does not match the expected one")
_ -> fail "error when setting inflate dictionary"