binrep-0.5.0: src/Binrep/Type/NullPadded.hs
{-# LANGUAGE OverloadedStrings #-}
module Binrep.Type.NullPadded where
import Binrep
import Bytezap.Bytes qualified as BZ
import FlatParse.Basic qualified as FP
import Control.Monad.Combinators qualified as Monad
import Binrep.Util ( tshow )
import Refined
import Refined.Unsafe
import GHC.TypeNats
import Util.TypeNats ( natValInt )
import Data.Typeable ( typeRep )
data NullPad (n :: Natural)
-- | A type which is to be null-padded to a given total length.
--
-- Given some @a :: 'NullPadded' n a@, it is guaranteed that
--
-- @
-- 'blen' a '<=' 'natValInt' \@n
-- @
--
-- thus
--
-- @
-- 'natValInt' \@n '-' 'blen' a '>=' 0
-- @
--
-- That is, the serialized stored data will not be longer than the total length.
--
-- The binrep instances are careful not to construct bytestrings unnecessarily.
type NullPadded n a = Refined (NullPad n) a
instance (BLen a, KnownNat n) => Predicate (NullPad n) a where
validate p a
| len <= n = success
| otherwise
= throwRefineOtherException (typeRep p) $
"too long: " <> tshow len <> " > " <> tshow n
where
n = natValInt @n
len = blen a
instance (BLen a, Put a, KnownNat n) => Put (NullPadded n a) where
put ra = put a <> BZ.pokeByteReplicate paddingLen 0x00
where
a = unrefine ra
paddingLen = natValInt @n - blen a
-- ^ refinement guarantees >=0
instance (BLen a, Get a, KnownNat n) => Get (NullPadded n a) where
get = do
a <- get
let paddingLen = natValInt @n - blen a
if paddingLen < 0
then eBase $ EFailNamed "TODO used to be EOverlong, cba"
else do
Monad.skipCount paddingLen (FP.word8 0x00)
pure $ reallyUnsafeRefine a