packages feed

borsh-0.1.0: src/Codec/Borsh/Internal/Util/ByteString.hs

module Codec.Borsh.Internal.Util.ByteString (
    peekByteString
  , splitAtEnd
  , lengthStrict
  , lengthLazy
  ) where

import Foreign
import System.IO.Unsafe (unsafePerformIO)

import qualified Data.ByteString          as S
import qualified Data.ByteString.Internal as S.Internal
import qualified Data.ByteString.Lazy     as L

import Codec.Borsh.Internal.Util.ByteSwap

-- | Peek at the start of the bytestring
--
-- If the bytestring is long enough, returns the value, the size of that value,
-- and the remaining bytes.
--
-- Implementation note: this could be simplified using @bytestring >= 0.11@, as
-- the @offset@ argument has been removed. As it stands, this implementation is
-- backwards compatible.
peekByteString :: forall a.
     ByteSwap a
  => S.ByteString
  -> Maybe (a, Word32, S.ByteString)
peekByteString bs
  | sizeA > len = Nothing
  | otherwise   = Just (
        fromLE . LE $ unsafePerformIO $ withForeignPtr (plusForeignPtr fPtr offset) (peek . cast)
      , fromIntegral sizeA
      , S.drop sizeA bs
      )
  where
    sizeA :: Int
    sizeA = sizeOf (undefined :: a)

    fPtr :: ForeignPtr Word8
    offset, len :: Int
    (fPtr, offset, len) = S.Internal.toForeignPtr bs

    cast :: Ptr Word8 -> Ptr a
    cast = castPtr

-- | /O(1)/ @splitAtEnd n xs@ is equivalent to @(takeEnd n xs, dropEnd n xs)@
--
-- > splitAtEnd 0 "abcde" == ("abcde", "")
-- > splitAtEnd 1 "abcde" == ("abcd", "e")
-- > splitAtEnd 5 "abcde" == ("", "abcde")
--
-- Edge cases, similar to behaviour of 'splitAt':

-- > splitAtEnd (-1) "abcde" == ("abcde", "") -- split before start
-- > splitAtEnd 6    "abcde" == ("", "abcde") -- split after end
splitAtEnd ::
     Int
  -> S.ByteString
  -> (S.ByteString, S.ByteString)
splitAtEnd n bs = S.splitAt n' bs
  where
    -- This may drop below zero if @n > length bs@. This will give us the
    -- correct behaviour from 'splitAt'
    n' :: Int
    n' = S.length bs - n

-- | Wrapper around 'S.length' with more sane return type
lengthStrict :: S.ByteString -> Word32
lengthStrict = fromIntegral . S.length

-- | Wrapper around 'L.length' with more sane return type
lengthLazy :: L.ByteString -> Word32
lengthLazy = fromIntegral . L.length