binrep-0.8.0: src/Binrep/Type/NullPadded.hs
-- | Data null-padded to a given length.
{-# LANGUAGE OverloadedStrings #-}
module Binrep.Type.NullPadded where
import Binrep
import Bytezap.Poke qualified as BZ
import Bytezap.Struct qualified as BZ.Struct
import FlatParse.Basic qualified as FP
import Raehik.Compat.FlatParse.Basic.WithLength qualified as FP
import Control.Monad.Combinators ( skipCount )
import Binrep.Util ( tshow )
import Refined
import Refined.Unsafe
import GHC.TypeNats
import Util.TypeNats ( natValInt )
import Data.Typeable ( typeRep )
import Bytezap.Parser.Struct qualified as BZG
import GHC.Exts ( Int(I#) )
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.
-}
type NullPadded n a = Refined (NullPad n) a
instance IsCBLen (NullPadded n a) where type CBLen (NullPadded n a) = n
deriving via ViaCBLen (NullPadded n a) instance KnownNat n => BLen (NullPadded n a)
-- | Assert that term will fit.
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, KnownNat n, Put a) => PutC (NullPadded n a) where
putC ra = BZ.Struct.sequencePokes (BZ.toStructPoke (put a)) len
(BZ.Struct.replicateByte paddingLen 0x00)
where
a = unrefine ra
len = blen a
paddingLen = natValInt @n - len
-- ^ refinement guarantees >=0
instance (BLen a, KnownNat n, Put a) => Put (NullPadded n a) where
put ra = put a <> BZ.replicateByte paddingLen 0x00
where
a = unrefine ra
paddingLen = natValInt @n - blen a
-- ^ refinement guarantees >=0
-- | Run a @Getter a@ isolated to @n@ bytes.
instance (KnownNat n, Get a) => GetC (NullPadded n a) where
getC = fpToBz get len# $ \a _unconsumed# ->
-- TODO consume nulls lol
BZG.constParse $ reallyUnsafeRefine a
where
!(I# len#) = natValInt @n
instance (Get a, KnownNat n) => Get (NullPadded n a) where
get = do
(a, len) <- FP.parseWithLength get
let paddingLen = natValInt @n - len
if paddingLen < 0
then eBase $ EFailNamed "TODO used to be EOverlong, cba"
else do
skipCount paddingLen (FP.word8 0x00)
pure $ reallyUnsafeRefine a