packages feed

binrep-0.1.0: src/Binrep/Predicates/NullPadTo.hs

{-# LANGUAGE OverloadedStrings #-}

module Binrep.Predicates.NullPadTo where

import Binrep.Codec
import Binrep.ByteLen
import Binrep.Util ( tshow )
import Refined
import Refined.WithRefine
import Data.Serialize
import GHC.TypeNats
import Numeric.Natural
import GHC.Natural ( minusNaturalMaybe )
import GHC.Exts ( proxy#, Proxy# )
import Data.Typeable
import Data.ByteString qualified as BS

data NullPadTo (n :: Nat)

instance KnownNat n => ByteLen (WithRefine 'Enforced (NullPadTo n) a) where
    blen = const n
      where n = natVal' (proxy# :: Proxy# n)

instance (ByteLen a, KnownNat n) => Predicate (NullPadTo n) a where
    validate p a
      | len > n
          = throwRefineOtherException (typeRep p) $
                   "too long: " <> tshow len <> " > " <> tshow n
      | otherwise = success
      where
        n = natVal' (proxy# :: Proxy# n)
        len = blen a

-- | predicate is inherently enforced due to checking length to calculate how
--   many succeeding nulls to parse
--
-- Note that the consumer probably doesn't care about the content of the
-- padding, just that the data is chunked correctly. I figure we care about
-- correctness here, so it'd be nice to know about the padding well-formedness
-- (i.e. that it's all nulls).
instance (BinaryCodec a, ByteLen a, KnownNat n)
      => BinaryCodec (WithRefine 'Enforced (NullPadTo n) a) where
    fromBin = do
        a <- fromBin
        let len = blen a
        case minusNaturalMaybe n len of
          Nothing -> fail $ "too long: " <> show len <> " > " <> show n
          Just nullstrLen -> do
            getNNulls nullstrLen
            return $ reallyUnsafeEnforce a
      where
        n = natVal' (proxy# :: Proxy# n)
    toBin wrnpa = do
        let npa = unWithRefine wrnpa
        toBin npa
        let paddingLength = n - blen npa
        putByteString $ BS.replicate (fromIntegral paddingLength) 0x00
      where
        n = natVal' (proxy# :: Proxy# n)

getNNulls :: Natural -> Get ()
getNNulls = \case 0 -> return ()
                  n -> getWord8 >>= \case
                         0x00    -> getNNulls $ n-1
                         nonNull -> do
                           offset <- bytesRead
                           fail $  "expected null, found: "<> show nonNull
                                <> " at offset " <> show offset
                                <> ", " <> show n <> " more nulls to go"