packages feed

binrep-0.5.0: src/Binrep/Type/NullTerminated.hs

{- | C-style null-terminated data.

I mix string and bytestring terminology here, due to bad C influences. This
module is specifically interested in bytestrings and their encoding. String/text
encoding is handled in 'Binrep.Type.Text'.
-}

{-# LANGUAGE OverloadedStrings #-} -- for refined errors

module Binrep.Type.NullTerminated where

import Binrep

import Binrep.Get.Flatparse qualified as Flatparse
import FlatParse.Basic qualified as FP

import Refined
import Refined.Unsafe
import Data.Typeable ( typeRep )

import Data.ByteString qualified as B
import Data.Word ( Word8 )

-- | Null-terminated data. Arbitrary length terminated with a null byte.
--   Permits no null bytes inside the data.
data NullTerminate
type NullTerminated = Refined NullTerminate

-- | Null-terminated data may not contain any null bytes.
instance NullCheck a => Predicate NullTerminate a where
    validate p a
     | hasNoNulls a = throwRefineOtherException (typeRep p) $
        "null byte not permitted in null-terminated data"
     | otherwise = success

class NullCheck a where hasNoNulls :: a -> Bool
instance NullCheck B.ByteString where
    {-# INLINE hasNoNulls #-}
    hasNoNulls = B.any (== 0x00)

instance BLen a => BLen (NullTerminated a) where
    blen ra = 1 + blen (unrefine ra)
    {-# INLINE blen #-}

-- | Serialization of null-terminated data may be defined generally using the
--   data's underlying serializer.
instance Put a => Put (NullTerminated a) where
    {-# INLINE put #-}
    put a = put (unrefine a) <> put @Word8 0x00

-- | Parse a null-terminated bytestring.
instance Flatparse.Get (NullTerminated B.ByteString) where
    {-# INLINE get #-}
    get = reallyUnsafeRefine <$> Flatparse.getEBase FP.anyCString (EFailNamed "cstring")

{-
I don't know how to do @[a]@. Either I nullterm each element, which is weird
because it's not required in all cases, or I don't, in which case the general
Put doesn't work. Nullterming every element feels weird anyway -- what about
[Word8]?

instance NullCheck a => NullCheck [a] where
    {-# INLINE hasNoNulls #-}
    hasNoNulls = all hasNoNulls
instance NullCheck Word8 where
    {-# INLINE hasNoNulls #-}
    hasNoNulls = \case 0x00 -> False
                       _    -> True
-}