binrep-0.3.0: src/Binrep/Example/Tar.hs
module Binrep.Example.Tar where
import Binrep
import Binrep.Generic
import Binrep.Type.NullPadded
import Binrep.Type.AsciiNat
import GHC.Generics ( Generic )
import Data.Word ( Word8 )
import GHC.TypeNats
import Data.ByteString qualified as B
import FlatParse.Basic qualified as FP
type BS = B.ByteString
-- | The naturals in tars are sized octal ASCII digit strings that end with a
-- null byte (and may start with leading ASCII zeroes). The size includes the
-- terminating null, so you get @n-1@ digits. What a farce.
--
-- Don't use this constructor directly! The size must be checked to ensure it
-- fits.
newtype TarNat n = TarNat { getTarNat :: AsciiNat 8 }
deriving stock (Generic, Show, Eq)
instance KnownNat n => BLen (TarNat n) where
type CBLen (TarNat n) = n
-- | No need to check for underflow etc. as TarNat guarantees good sizing.
instance KnownNat n => Put (TarNat n) where
put (TarNat an) = put pfxNulls <> put an <> put @Word8 0x00
where
pfxNulls = B.replicate (fromIntegral pfxNullCount) 0x30
pfxNullCount = n - blen an - 1
n = typeNatToBLen @n
instance KnownNat n => Get (TarNat n) where
get = do
an <- FP.isolate (fromIntegral (n - 1)) get
get @Word8 >>= \case
0x00 -> return $ TarNat an
w -> eBase $ EExpectedByte 0x00 w
where
n = typeNatToBLen @n
-- Partial header
data Tar = Tar
{ tarFileName :: NullPadded 100 BS
, tarFileMode :: TarNat 8
, tarFileUIDOwner :: TarNat 8
, tarFileUIDGroup :: TarNat 8
, tarFileFileSize :: TarNat 12
, tarFileLastMod :: TarNat 12
} deriving stock (Generic, Show, Eq)
instance BLen Tar where blen = blenGeneric cNoSum
instance Put Tar where put = putGeneric cNoSum
instance Get Tar where get = getGeneric cNoSum