packages feed

snappy-c-0.1.0: src/Codec/Compression/SnappyC/Raw.hs

-- |
-- Module     : Codec.Compression.SnappyC.Raw
-- Copyright  : (c) 2024 Finley McIlwaine
-- License    : BSD-3-Clause (see LICENSE)
--
-- Maintainer : Finley McIlwaine <finley@well-typed.com>
--
-- Raw format Snappy compression/decompression.
--
-- > import Codec.Compression.SnappyC.Raw qualified as Snappy

module Codec.Compression.SnappyC.Raw
  ( -- * Compression
    compress
    -- * Decompression
  , decompress
  ) where

import Codec.Compression.SnappyC.Internal.C qualified as C

import Data.ByteString.Internal (ByteString(..))
import Foreign
import System.IO.Unsafe

-- | Compress the input using [Snappy](https://github.com/google/snappy/).
--
-- The result is in Snappy raw format, /not/ the framing format.
compress :: ByteString -> ByteString
compress (BS sfp slen) =
    unsafePerformIO $ do
      let dlen = C.snappy_max_compressed_length (fromIntegral slen)
      dfp <- mallocForeignPtrBytes (fromIntegral dlen)
      withForeignPtr sfp $ \sptr ->
        withForeignPtr dfp $ \dptr ->
          with dlen $ \dlen_ptr ->
            case
              C.snappy_compress
                (castPtr sptr)
                (fromIntegral slen)
                (castPtr dptr)
                dlen_ptr
            of
              0 ->
                BS dfp . fromIntegral <$> peek dlen_ptr
              1 ->
                error "impossible: there is no invalid input for compression"
              2 ->
                error "impossible: the buffer size is always set correctly"
              status ->
                error $
                  "impossible: unexpected status from snappy_compress: " ++
                  show status

-- | Decompress the input using [Snappy](https://github.com/google/snappy/).
--
-- Returns 'Nothing' if the input is not in Snappy raw format or
-- otherwise ill-formed.
decompress :: ByteString -> Maybe ByteString
decompress (BS sfp slen) =
    unsafePerformIO $ do
      withForeignPtr sfp $
        \sptr ->
          alloca $
            \dlen_ptr ->
              case
                C.snappy_uncompressed_length
                  (castPtr sptr)
                  (fromIntegral slen)
                  dlen_ptr
              of
                0 -> do
                  dlen <- fromIntegral <$> peek dlen_ptr
                  dfp <- mallocForeignPtrBytes dlen
                  withForeignPtr dfp $
                    \dptr ->
                      case
                          C.snappy_uncompress
                            (castPtr sptr)
                            (fromIntegral slen)
                            (castPtr dptr)
                            dlen_ptr
                      of
                        0 ->
                          Just . BS dfp . fromIntegral <$> peek dlen_ptr
                        1 ->
                          -- Invalid input. Successful result from
                          -- snappy_uncompressed_length does *not* mean the
                          -- input is completely valid
                          return Nothing
                        status ->
                          error $
                            "impossible: decompression failed with status " ++
                            show status
                1 ->
                  return Nothing
                status ->
                  error $
                    "impossible: snappy_uncompressed_length failed with " ++
                    "status" ++ show status