packages feed

binrep-0.2.0: src/Binrep/Type/NullPadded.hs

{-# LANGUAGE OverloadedStrings #-}

module Binrep.Type.NullPadded where

import Binrep
import Binrep.Util ( tshow )

import Refined
import Refined.Unsafe

import GHC.TypeNats
import Data.Typeable ( typeRep )
import FlatParse.Basic qualified as FP
import FlatParse.Basic ( Parser )
import Mason.Builder qualified as Mason
import Data.ByteString qualified as BS

data NullPad (n :: Natural)

type NullPadded n a = Refined (NullPad n) a

-- | The size of some null-padded data is known - at compile time!
type instance CBLen (NullPadded n a) = n

deriving anyclass instance KnownNat n => BLen (NullPadded n a)

instance (BLen a, KnownNat n) => Predicate (NullPad n) a where
    validate p a
      | len > n
          = throwRefineOtherException (typeRep p) $
                   "too long: " <> tshow len <> " > " <> tshow n
      | otherwise = success
      where
        n = typeNatToBLen @n
        len = blen a

-- TODO cleanup
instance (Put a, BLen a, KnownNat n) => Put (NullPadded n a) where
    put wrnpa =
        let npa = unrefine wrnpa
            paddingLength = n - blen npa
         in put npa <> Mason.byteString (BS.replicate (fromIntegral paddingLength) 0x00)
      where
        n = typeNatToBLen @n

-- | Safety: we assert actual length is within expected length (in order to
--   calculate how much padding 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).
--
-- TODO maybe better definition via isolate
instance (Get a, BLen a, KnownNat n) => Get (NullPadded n a) where
    get = do
        a <- get
        let len = blen a
            nullStrLen = n - len
        if   nullStrLen < 0
        then FP.err $ "too long: " <> show len <> " > " <> show n
        else getNNulls nullStrLen >> return (reallyUnsafeRefine a)
      where
        n = typeNatToBLen @n

getNNulls :: BLenT -> Parser String ()
getNNulls = \case 0 -> return ()
                  n -> FP.anyWord8 >>= \case
                         0x00    -> getNNulls $ n-1
                         nonNull -> FP.err "TODO expected null, wasn't"