lz4-hs-0.1.2.0: src/Codec/Lz4.hs
-- | The functions in this module throw exceptions on error.
--
-- 'decompress' and 'compress' are fully lazy, i.e. memory efficient.
module Codec.Lz4 ( -- * Functions for working with blocks
compressBlock
, decompressBlockSz
, lZ4MaxInputSize
, compressBlockHC
, lZ4HCClevelMax
-- * Functions for working with frames
, compress
, decompress
-- * Version info
, lZ4VersionNumber
, lZ4VersionString
) where
import Codec.Lz4.Foreign
import Control.Monad (when)
import Control.Monad.ST.Lazy (runST)
import qualified Control.Monad.ST.Lazy as LazyST
import qualified Control.Monad.ST.Lazy.Unsafe as LazyST
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Unsafe as BS
import Foreign.C.String (CString)
import Foreign.C.Types (CInt)
import Foreign.ForeignPtr (ForeignPtr, castForeignPtr,
mallocForeignPtrBytes,
newForeignPtr, withForeignPtr)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr (castPtr, nullPtr)
import Foreign.Storable (peek, poke)
import System.IO.Unsafe (unsafePerformIO)
check :: LZ4FErrorCode -> IO ()
check err = when (lZ4FIsError err) $ error (lZ4FGetErrorName err)
-- | Lazily decompress a frame
decompress :: BSL.ByteString -> BSL.ByteString
decompress bs = runST $ do
let bss = BSL.toChunks bs
(ctx, buf) <- LazyST.unsafeIOToST $ do
(err, preCtx) <- lZ4FCreateDecompressionContext lZ4FGetVersion
check err
ctx <- castForeignPtr <$> newForeignPtr lZ4FFreeCompressionContext (castPtr preCtx)
dstBuf <- mallocForeignPtrBytes (64 * 1024)
pure (ctx, dstBuf)
BSL.fromChunks <$> loop ctx buf bss
where loop :: LzDecompressionCtxPtr -> ForeignPtr a -> [BS.ByteString] -> LazyST.ST s [BS.ByteString]
loop _ _ [] = pure []
loop ctx buf (b:bs') = do
(nxt, res) <- stepChunk ctx buf b
case nxt of
Nothing -> (res:) <$> loop ctx buf bs'
Just next -> (res:) <$> loop ctx buf (next:bs')
stepChunk :: LzDecompressionCtxPtr -> ForeignPtr a -> BS.ByteString -> LazyST.ST s (Maybe BS.ByteString, BS.ByteString)
stepChunk ctx dst b = LazyST.unsafeIOToST $
BS.unsafeUseAsCStringLen b $ \(buf, sz) ->
withForeignPtr dst $ \d ->
alloca $ \dSzPtr ->
alloca $ \szPtr -> do
poke dSzPtr (64 * 1024)
poke szPtr (fromIntegral sz)
res <- lZ4FDecompress ctx d dSzPtr buf szPtr nullPtr
check res
bRead <- peek szPtr
bWritten <- peek dSzPtr
outBs <- BS.packCStringLen (castPtr d, fromIntegral bWritten)
let remBs = if fromIntegral bRead == sz
then Nothing
else Just (BS.drop (fromIntegral bRead) b)
pure (remBs, outBs)
-- | Lazily compress a frame.
compress :: BSL.ByteString -> BSL.ByteString
compress bs = runST $ do
let bss = BSL.toChunks bs
(ctx, header) <- initCtx
rest <- loop ctx bss
pure $ BSL.fromChunks (header:rest)
where initCtx :: LazyST.ST s (LzCtxPtr, BS.ByteString)
initCtx = LazyST.unsafeIOToST $ do
(err, preCtx) <- lZ4FCreateCompressionContext lZ4FGetVersion
check err
ctx <- castForeignPtr <$> newForeignPtr lZ4FFreeCompressionContext (castPtr preCtx)
dst <- mallocForeignPtrBytes lZ4FHeaderSizeMax
header <- withForeignPtr dst $ \d -> do
res <- lZ4FCompressBegin ctx d lZ4FHeaderSizeMax nullPtr
check res
BS.packCStringLen (castPtr d, fromIntegral res)
pure (ctx, header)
loop :: LzCtxPtr -> [BS.ByteString] -> LazyST.ST s [BS.ByteString]
loop ctx [] = pure <$> finish ctx
loop ctx (b:bs') = (:) <$> update ctx b <*> loop ctx bs'
finish :: LzCtxPtr -> LazyST.ST s BS.ByteString
finish ctx = LazyST.unsafeIOToST $ do
let expectedSz = lZ4FCompressBound 0 nullPtr
dst <- mallocForeignPtrBytes (fromIntegral expectedSz)
withForeignPtr dst $ \d -> do
res <- lZ4FCompressEnd ctx d expectedSz nullPtr
check res
BS.packCStringLen (castPtr d, fromIntegral res)
update :: LzCtxPtr -> BS.ByteString -> LazyST.ST s BS.ByteString
update ctx b = LazyST.unsafeIOToST $
BS.unsafeUseAsCStringLen b $ \(buf, sz) -> do
let expectedSz = lZ4FCompressBound (fromIntegral sz) nullPtr
dst <- mallocForeignPtrBytes (fromIntegral expectedSz)
withForeignPtr dst $ \d -> do
res <- lZ4FCompressUpdate ctx d expectedSz buf (fromIntegral sz) nullPtr
check res
BS.packCStringLen (castPtr d, fromIntegral res)
{-# NOINLINE compressBlock #-}
compressBlock :: BS.ByteString -> BS.ByteString
compressBlock = unsafePerformIO . compressBlockGeneric lZ4CompressDefault
{-# NOINLINE compressBlockHC #-}
-- | @since 0.1.1.0
compressBlockHC :: Int -- ^ Compression level (must be less than 'lZ4HCClevelMax')
-> BS.ByteString
-> BS.ByteString
compressBlockHC lvl = unsafePerformIO . compressBlockGeneric (\src dst ssz dsz -> lZ4CompressHC src dst ssz dsz (fromIntegral lvl))
compressBlockGeneric :: (CString -> CString -> CInt -> CInt -> IO CInt) -> BS.ByteString -> IO BS.ByteString
compressBlockGeneric cfun bs =
BS.unsafeUseAsCStringLen bs $ \(buf, sz) -> do
let resSz = lZ4CompressBound (fromIntegral sz)
dst <- mallocForeignPtrBytes (fromIntegral resSz)
withForeignPtr dst $ \d -> do
bWritten <- cfun buf d (fromIntegral sz) resSz
when (bWritten == 0) $ error "Compression error"
pure $ BS.fromForeignPtr (castForeignPtr dst) 0 (fromIntegral bWritten)
{-# NOINLINE decompressBlockSz #-}
-- | Decompress a block. The size of the uncompressed data must be known.
decompressBlockSz :: BS.ByteString
-> Int -- ^ Decompressed size
-> BS.ByteString
decompressBlockSz bs expectedSz = unsafePerformIO $
BS.unsafeUseAsCStringLen bs $ \(buf, sz) -> do
dst <- mallocForeignPtrBytes expectedSz
withForeignPtr dst $ \d -> do
bWritten <- lZ4DecompressSafe buf d (fromIntegral sz) (fromIntegral expectedSz)
when (bWritten < 0) $ error "Decompression error"
pure $ BS.fromForeignPtr (castForeignPtr dst) 0 expectedSz