packages feed

libasterix-0.14.0: src/Asterix/BitString.hs

-- |
-- Module: Asterix.BitString
--
-- Bits and bytes manipulation module.

{-# LANGUAGE LambdaCase #-}

module Asterix.BitString where

import           Data.Bits               (complement, shift, testBit, (.&.),
                                          (.|.))
import           Data.Bool
import           Data.ByteString         (ByteString)
import qualified Data.ByteString         as BS
import qualified Data.ByteString.Base16  as B16
import           Data.ByteString.Builder as BSB
import qualified Data.ByteString.Char8   as BS8
import qualified Data.ByteString.Lazy    as BSL
import           Data.Coerce
import qualified Data.List               as L
import           Data.List.NonEmpty      (NonEmpty (..), uncons)
import           Data.Maybe
import           Data.Word
import           GHC.Stack

-- | numBytes and numBits function overloading for various structures.
class IsNumBits t where
    numBytes :: t -> Int
    numBits :: t -> Int

-- | Number of bits, stored as 'divMod n 8'.
data NumBits = NumBits
    { _numBytes :: !Int
    , _numBits  :: !Int
    } deriving (Eq, Show)

-- | Convert structure's number of bits to Int.
numBitsToInt :: IsNumBits t => t -> Int
numBitsToInt x = numBytes x * 8 + numBits x

-- | Convert Int to NumBits.
intToNumBits :: Coercible NumBits t => Int -> t
intToNumBits i = coerce (uncurry NumBits $ divMod i 8)

instance IsNumBits NumBits where
    numBytes = _numBytes
    numBits = _numBits

instance Num NumBits where
    a + b = intToNumBits (numBitsToInt a + numBitsToInt b)
    a * b = intToNumBits (numBitsToInt a * numBitsToInt b)
    abs = intToNumBits . abs . numBitsToInt
    signum = intToNumBits . signum . numBitsToInt
    fromInteger = intToNumBits . fromIntegral
    negate = intToNumBits . negate . numBitsToInt

instance Ord NumBits where
    compare (NumBits a1 a2) (NumBits b1 b2)
        = compare a1 b1
        <> compare a2 b2

-- | Bit offset.
newtype Offset = Offset NumBits deriving (Eq, Ord, Num, Show, IsNumBits)

-- | Bit size.
newtype Size = Size NumBits deriving (Eq, Ord, Num, Show, IsNumBits)

-- | A bitstring, capturing fragment of a ByteString.
data Bits = Bits
    { bitsData   :: ByteString
    , bitsOffset :: !Offset
    , bitsSize   :: !Size
    } deriving Show

-- | ByteString Builder with known byte size.
data SBuilder = SBuilder
    { sbByteSize :: Int
    , sbData     :: Builder
    } deriving Show

-- | For types that can be converted to bits.
class ToBits t where
    toBits :: t -> Bits

-- | For types that can be converted to/from ByteString.
-- Be aware that some conversions might be slow.
class ToFromByteString t where
    toByteString :: t -> ByteString
    fromByteString :: ByteString -> t

-- | Convert bytestring to hex representation.
hexlify :: BS.ByteString -> String
hexlify = BS8.unpack . B16.encode

-- | Convert hex representation back to a bytestring.
unhexlify :: String -> Maybe BS.ByteString
unhexlify = either (const Nothing) Just . B16.decode . BS8.pack

-- | Helper function for expression evaluation.
withAssumption :: HasCallStack => Bool -> a -> a
withAssumption False _  =
    error $ "Internal error (wrong assumption), " <> prettyCallStack callStack
withAssumption True val = val

-- | Conversion from 'Builder' to 'ByteString'.
builderToByteStringSlow :: Builder -> ByteString
builderToByteStringSlow = BSL.toStrict . BSB.toLazyByteString

-- | Conversion from 'ByteString' to unsigned number.
byteStringToNum :: Num a => ByteString -> a
byteStringToNum bs
    | BS.null bs = 0
    | otherwise =
        let (xs, x) = (BS.init bs, BS.last bs)
        in byteStringToNum xs * 256 + fromIntegral x

-- | Calculate 'left' and 'right' bit alignment.
alignment :: Bits -> (Int, Int)
alignment (Bits _ o n) = (a, b)
  where
    a = numBits o
    b = numBits (o + coerce n)

-- | Calculate 'left' bit alignment.
leftAlignment :: Bits -> Int
leftAlignment = fst . alignment

-- | Calculate 'right' bit alignment.
rightAlignment :: Bits -> Int
rightAlignment = snd . alignment

-- | Test whether 'Bits' are empty.
nullBits :: Bits -> Bool
nullBits = (<= 0) . bitsSize

-- | Convert from 'ByteString' to 'Bits'.
byteStringToBits :: ByteString -> Bits
byteStringToBits bs = Bits bs 0 (intToNumBits $ BS.length bs * 8)

-- | Calculate required bytes + additional bits.
requiredBytes :: Int -> Int -> (Int, Int)
requiredBytes o8 n = divMod (o8 + n) 8

-- | Convert 'Integer' to 'Bits'.
integerToBits :: Int -> Int -> Integer -> Bits
integerToBits o8 n val = Bits bs o (Size $ intToNumBits n)
  where
    o = Offset (NumBits 0 o8)
    (m, b) = withAssumption (n >= 0) requiredBytes o8 n
    m' = m + bool 1 0 (b == 0)
    shiftedVal
        | b == 0 = val
        | otherwise = shift val (8 - b)
    byteList x = \case
        0 -> []
        k ->
            let (x1, x2) = divMod x 256
            in fromInteger x2 : byteList x1 (pred k)
    bs = BS.pack (reverse $ byteList shiftedVal m')

-- | Split 'Word8' to 8 boolean flags.
word8ToBools :: Word8 -> [Bool]
word8ToBools w = [testBit w i | i <- [7,6..0]]

-- | Fold boolean flags to Num.
boolsToNum :: Num a => [Bool] -> a
boolsToNum = go . Prelude.reverse
  where
    go = \case
        [] -> 0
        (x:xs) -> go xs * 2 + bool 0 1 x

-- | Convert list of bool flags to bits.
boolsToBits :: Int -> [Bool] -> Bits
boolsToBits o8 lst = Bits bs (Offset $ intToNumBits o8) (Size $ intToNumBits n)
  where
    n = Prelude.length lst
    prefix = replicate o8 False
    bs = BS.pack $ byteList (prefix <> lst)
    byteList i =
        let (a, b) = splitAt 8 i
        in case Prelude.length a < 8 of
            True  -> [boolsToNum $ take 8 $ a <> repeat False]
            False -> boolsToNum a : byteList b

-- | Calculate 'compact' version of Bits - helper function.
compactBits :: Bits -> (ByteString, Maybe Word8)
compactBits (Bits bs o n) = (bs', padding)
  where
    k = numBytes o
    (m, b) = requiredBytes (numBits o) (numBitsToInt n)
    bs' = BS.take m $ BS.drop k bs
    padding
        | b == 0 = Nothing
        | otherwise = Just $ BS.index bs (k+m)

-- | Append properly aligned bits.
appendBits :: Bits -> Bits -> Bits
appendBits s1 s2 = withAssumption (rightAlignment s1 == leftAlignment s2) go
  where
    (a1, a2) = compactBits s1
    (b1, b2) = compactBits s2
    padding = maybe BS.empty BS.singleton b2
    bs = case a2 of
        Nothing -> a1 <> b1 <> padding
        Just w1 ->
            let w2 = case BS.null b1 of
                    False -> BS.head b1
                    True  -> fromJust b2
                m = shift 0xff (- rightAlignment s1)
                w = (w1 .&. complement m) .|. (w2 .&. m)
            in a1 <> BS.singleton w <> bool (BS.tail b1) mempty (BS.null b1) <> padding
    o = Offset $ NumBits 0 $ leftAlignment s1
    n = bitsSize s1 + bitsSize s2
    go
        | nullBits s1 = s2
        | nullBits s2 = s1
        | otherwise = Bits bs o n

-- | Concatinate non-empty list of 'Bits'.
concatBits :: NonEmpty Bits -> Bits
concatBits lst = case uncons lst of
    (x, Nothing) -> x
    (x, Just xs) -> appendBits x (Asterix.BitString.concatBits xs)

-- | Convert Bits to Integral.
bitsToNum :: Integral a => Bits -> a
bitsToNum s = case n of
    0 -> 0
    _ ->
        let (bs, mw) = compactBits s
            val1 = byteStringToNum bs
            (a, b) = alignment s
            val2 = val1 * (2 ^ b) + fromIntegral (shift (fromJust mw) (- (8-b)))
        in case (a, b) of
            (0, 0) -> val1
            (_, 0) -> mod val1 (2 ^ n)
            (0, _) -> val2
            (_, _) -> mod val2 (2 ^ n)
  where
    n = numBitsToInt $ bitsSize s

-- | Extract 'Bits' to list of bool flags.
bitsToBools :: Bits -> [Bool]
bitsToBools (Bits bs o n') =
    let n = numBitsToInt n'
        o8 = numBits o
        (m, _b) = requiredBytes o8 n
        s = BS.take (succ m) $ BS.drop (numBytes o) bs
        lst = mconcat (word8ToBools <$> BS.unpack s)
    in Prelude.take n $ drop o8 lst

-- | Convert properly aligned 'Bits' to 'Builder'.
bitsToBuilder :: Bits -> Builder
bitsToBuilder s@(Bits bs o n') = withAssumption (alignment s == (0, 0)) bld
  where
    n = numBitsToInt n'
    o8 = numBits o
    (m, _b) = requiredBytes o8 n
    bld = BSB.byteString $ BS.take m $ BS.drop (numBytes o) bs

-- | Convert properly aligned 'Bits' to 'SBuilder'.
bitsToSBuilder :: Bits -> SBuilder
bitsToSBuilder arg = SBuilder
    (numBytes $ bitsSize arg)
    (bitsToBuilder arg) -- this call contains 'withAssumption'

-- | Convert 'Word8' to 'SBuilder'.
word8ToSBuilder :: Word8 -> SBuilder
word8ToSBuilder = SBuilder 1 . BSB.word8

-- | Show value as binary string.
debugBits :: ToBits t => t -> String
debugBits val = mconcat $ L.intersperse " " (goOctet <$> octets)
  where
    Bits bs o n' = toBits val
    n = numBitsToInt n'
    o8 = numBits o
    (m, b) = requiredBytes o8 n
    k = bool m (pred m) (b == 0)
    a = numBytes o
    octets = [a .. (a+k)]
    goOctet ix =
        let w = BS.index bs ix
        in do
            i <- [7,6..0]
            let j = 7 - i
                x = bool '0' '1' $ testBit w i
                o2 = Offset $ NumBits ix j
            pure $ bool '.' x (o2 >= o && o2 < (o + coerce n'))

instance Eq Bits where
    b1 == b2
        = numBits (bitsOffset b1) == numBits (bitsOffset b2)
        && bitsToBools b1 == bitsToBools b2

instance ToBits Bits where
    toBits = id

instance ToBits ByteString where
    toBits = byteStringToBits

instance ToBits Builder where
    toBits = toBits . BSL.toStrict . BSB.toLazyByteString

instance ToBits SBuilder where
    toBits = toBits . sbData

instance Semigroup SBuilder where
    SBuilder a1 a2 <> SBuilder b1 b2 = SBuilder (a1+b1) (a2 <> b2)

instance Monoid SBuilder where
    mempty = SBuilder 0 mempty

instance ToFromByteString ByteString where
    toByteString = id
    fromByteString = id

instance ToFromByteString Builder where
    toByteString = BSL.toStrict . BSB.toLazyByteString
    fromByteString = BSB.byteString

instance ToFromByteString SBuilder where
    toByteString = toByteString . sbData
    fromByteString s = SBuilder (BS.length s) (BSB.byteString s)