packages feed

age-0.0.1.0: src/Crypto/Age/Payload/Plaintext.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}

-- | Plaintext chunks of an age file payload.
module Crypto.Age.Payload.Plaintext
  ( -- * Plaintext payload chunk
    PlaintextPayloadChunk (..)
  , plaintextPayloadChunkToBytes
  , plaintextPayloadChunkParser
  , plaintextChunkSize
    -- ** Normal chunk
  , NormalPlaintextPayloadChunk (NormalPlaintextPayloadChunk)
  , mkNormalPlaintextPayloadChunk
  , normalPlaintextPayloadChunkParser
    -- ** Final chunk
  , FinalPlaintextPayloadChunk (FinalPlaintextPayloadChunk)
  , mkFinalPlaintextPayloadChunk
  , finalPlaintextPayloadChunkParser
  ) where

import Control.Applicative ( Alternative (..) )
import Control.Monad ( void )
import Data.Attoparsec.ByteString ( Parser, atEnd, endOfInput, take )
import Data.Attoparsec.ByteString.Extra ( takeWhileMN )
import Data.ByteString ( ByteString )
import qualified Data.ByteString as BS
import Prelude hiding ( take )

-- | Size of a full plaintext payload chunk (64 KiB).
plaintextChunkSize :: Int
plaintextChunkSize = 64 * 1024

-------------------------------------------------------------------------------
-- Normal plaintext payload chunk
-------------------------------------------------------------------------------

-- | Normal chunk of plaintext data in a payload (i.e. not the final chunk).
newtype NormalPlaintextPayloadChunk = MkNormalPlaintextPayloadChunk
  { unNormalPlaintextPayloadChunk :: ByteString }
  deriving newtype (Show, Eq)

pattern NormalPlaintextPayloadChunk :: ByteString -> NormalPlaintextPayloadChunk
pattern NormalPlaintextPayloadChunk bs <- MkNormalPlaintextPayloadChunk bs

{-# COMPLETE NormalPlaintextPayloadChunk #-}

-- | Construct a 64 KiB plaintext payload chunk from a byte string.
mkNormalPlaintextPayloadChunk :: ByteString -> Maybe NormalPlaintextPayloadChunk
mkNormalPlaintextPayloadChunk bs
  | BS.length bs == plaintextChunkSize = Just (MkNormalPlaintextPayloadChunk bs)
  | otherwise = Nothing

normalPlaintextPayloadChunkParser :: Parser NormalPlaintextPayloadChunk
normalPlaintextPayloadChunkParser = do
  bs <- take plaintextChunkSize
  atEnd >>= \case
    True -> fail "expected more input"
    False -> pure (MkNormalPlaintextPayloadChunk bs)

-------------------------------------------------------------------------------
-- Final plaintext payload chunk
-------------------------------------------------------------------------------

-- | Final chunk of plaintext data in a payload.
newtype FinalPlaintextPayloadChunk = MkFinalPlaintextPayloadChunk
  { unFinalPlaintextPayloadChunk :: ByteString }
  deriving newtype (Show, Eq)

pattern FinalPlaintextPayloadChunk :: ByteString -> FinalPlaintextPayloadChunk
pattern FinalPlaintextPayloadChunk bs <- MkFinalPlaintextPayloadChunk bs

{-# COMPLETE FinalPlaintextPayloadChunk #-}

-- | Construct the final chunk of data in a payload from a byte string that is
-- 64 KiB or smaller.
mkFinalPlaintextPayloadChunk :: ByteString -> Maybe FinalPlaintextPayloadChunk
mkFinalPlaintextPayloadChunk bs
  | BS.length bs <= plaintextChunkSize = Just (MkFinalPlaintextPayloadChunk bs)
  | otherwise = Nothing

finalPlaintextPayloadChunkParser :: Parser FinalPlaintextPayloadChunk
finalPlaintextPayloadChunkParser = do
  bs <- takeWhileMN 0 (fromIntegral plaintextChunkSize) (const True)
  void endOfInput
  pure (MkFinalPlaintextPayloadChunk bs)

-------------------------------------------------------------------------------
-- Plaintext payload chunk
-------------------------------------------------------------------------------

-- | Chunk of data in an plaintext payload.
data PlaintextPayloadChunk
  = -- | Normal chunk of plaintext (i.e. not the final chunk).
    PlaintextPayloadChunkNormal !NormalPlaintextPayloadChunk
  | -- | Final chunk of plaintext.
    PlaintextPayloadChunkFinal !FinalPlaintextPayloadChunk
  deriving stock (Show, Eq)

plaintextPayloadChunkToBytes :: PlaintextPayloadChunk -> ByteString
plaintextPayloadChunkToBytes = \case
  PlaintextPayloadChunkNormal c -> unNormalPlaintextPayloadChunk c
  PlaintextPayloadChunkFinal c -> unFinalPlaintextPayloadChunk c

plaintextPayloadChunkParser :: Parser PlaintextPayloadChunk
plaintextPayloadChunkParser =
  (PlaintextPayloadChunkNormal <$> normalPlaintextPayloadChunkParser)
    <|> (PlaintextPayloadChunkFinal <$> finalPlaintextPayloadChunkParser)