conduit-network-stream-0.2: src/Data/Conduit/Network/Stream/Header.hs
-- | The network stream header design is inspired by the variable length
-- integers used in Googles Protocol Buffers (protobuf): Each stream block is
-- preceded by a binary header of variable length. There are currently 3 types
-- of headers:
--
-- * The beginning of a conduit block is encoded as @[0, MSB]@
-- * The end of a conduit block is encoded as @[1, MSB]@
-- * Length definition of the current block, encoded as variable length integer
--
-- Each header byte consists of 7 bits + the "most significant bit". For
-- example, the number 256 in its binary representation is:
--
-- > 1 0000 0000
--
-- First, split the number into 7-bit packages (add 0s to the front if
-- necessary):
--
-- > 0000 010 0000 000
--
-- These 7-bit packages are then reversed in order and encoded as 8-bit bytes
-- where the last bit marks the end of our header if set, for example:
--
-- > 0000 000 0000 010 -- reverse order
-- > 0000 0000 0000 0101 -- set 8th bit
-- > _ X -- (unset = _, set = X):
--
-- Another example would be the representation of the number 65536, or
--
-- > 1 0000 0000 0000 0000
--
-- as
--
-- > 0000 100 0000 000 0000 000 -- 7 bit packages
-- > 0000 000 0000 000 0000 100 -- reverse order
-- > 0000 0000 0000 0000 0000 1001 -- set 8th bit
-- > _ _ X
--
-- Distinction between sepcial headers (such as the start or end of conduit
-- blocks) and regular variable length integers is done by checking the most
-- significant (i.e. last) byte. In a variable length integer, the first 7 bits
-- of the MSB are always bigger than 0, while special headers are ended by the
-- byte "00000001":
--
-- > 0000 0000 0000 0001 -- special header: conduit block start
-- > 0000 0010 0000 0001 -- special header: conduit block end
-- > 0000 0011 -- block of length 1
-- > 0000 0101 -- block of length 2
-- > 0000 1001 -- block of length 4
-- > 1111 1111 -- block of length 127
-- > 0000 0000 0000 0011 -- block of length 128 (note the 7 bit shift)
-- > 0000 0010 0000 0011 -- block of length 129
module Data.Conduit.Network.Stream.Header where
import Data.Bits
--import Data.Enum
import Data.Word
import Data.Conduit
-- import Data.Conduit.Network.Stream.Exceptions
import qualified Data.Conduit.Binary as CB
import qualified Data.ByteString as BS
--import qualified Data.ByteString.Lazy as BL
data Header
= ConduitSTART
| ConduitEND
| VarInt Int
| InvalidHeader [Word8]
| EndOfInput
deriving (Show)
-- | Make the current \"7-bit byte\" the most significant byte in the header
mkMSB :: Word8 -> Word8
mkMSB = setBit `flip` 7
specialHeaderMSB :: Word8
specialHeaderMSB = mkMSB 0
-- | Test wether or not a byte is the most significant byte of a special header
-- (i.e. 8th bit = 1, rest = 0)
isSpecialHeaderMSB :: Word8 -> Bool
isSpecialHeaderMSB = (specialHeaderMSB ==)
-- | Test wether or not a byte is the most significant byte (i.e. the last byte
-- of a header block)
isMSB :: Word8 -> Bool
isMSB = testBit `flip` 7
varint :: (Show int, Integral int) => int -> [Word8]
varint int = go (fromIntegral int :: Integer) []
where
go i l =
let w8 = fromIntegral $ 127 .&. i -- take the first 7 bits
r = shiftR i 7 -- shift the rest of the bits to the right
in if r == 0
then l ++ [mkMSB w8]
else go r (l ++ [w8])
fromVarint :: (Show int, Integral int, Bits int) => [Word8] -> int
fromVarint [] = 0
fromVarint [x] = fromIntegral $ x `clearBit` 7
fromVarint (w:r) = fromIntegral w + shiftL (fromVarint r) 7
condStart, condEnd :: [Word8]
condStart = [0, mkMSB 0]
condEnd = [1, mkMSB 0]
-- | A decode 'ByteString' sink which returns the current header
decodeHeader :: Monad m => Consumer BS.ByteString m Header
decodeHeader = go []
where
go w8s = do
h <- CB.head
case h of
Nothing -> return EndOfInput
Just w8 | isSpecialHeaderMSB w8 -> spec w8s
| isMSB w8 -> var (w8s ++ [w8])
| otherwise -> go (w8s ++ [w8])
-- special header decoding
spec [0] = return ConduitSTART
spec [1] = return ConduitEND
spec w8s = return $ InvalidHeader w8s
-- var int decoding
var vi = return $ VarInt (fromVarint vi)
encodeHeader :: Header -> Maybe BS.ByteString
encodeHeader ConduitSTART = Just $ BS.pack condStart
encodeHeader ConduitEND = Just $ BS.pack condEnd
encodeHeader (VarInt vi) = Just $ BS.pack (varint vi)
encodeHeader _ = Nothing