libasterix-0.18.0: src/Asterix/BitString.hs
-- |
-- Module: Asterix.BitString
--
-- Bits and bytes manipulation module.
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Asterix.BitString where
import Data.Bits (complement, shift, testBit, (.&.),
(.|.))
import Data.Bool
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString.Builder as BSB
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.Text (Text)
import qualified Data.Text as T
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
-- | 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
-- | Convert 'ByteString' to 'SBuilder'.
byteStringToSBuilder :: ByteString -> SBuilder
byteStringToSBuilder bs = SBuilder (BS.length bs) (byteString bs)
-- | Convert 'LazyByteString' to 'SBuilder'.
lazyByteStringToSBuilder :: BSL.ByteString -> SBuilder
lazyByteStringToSBuilder bs = SBuilder
(fromIntegral $ BSL.length bs)
(lazyByteString bs)
-- | Show value as binary string.
debugBits :: ToBits t => t -> Text
debugBits val = mconcat $ L.intersperse " " (T.pack . 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 :: [Int]
octets = [a .. (a+k)]
goOctet :: Int -> String
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)