{-# LANGUAGE CPP #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Thrift.Protocol.Binary
( module Thrift.Protocol
, BinaryProtocol(..)
, versionMask
, version1
) where
import Control.Exception ( throw )
import Control.Monad
import Data.Bits
import Data.ByteString.Lazy.Builder
import Data.Functor
import Data.Int
import Data.Monoid
import Data.Text.Lazy.Encoding ( decodeUtf8, encodeUtf8 )
import Data.Word
import Thrift.Protocol
import Thrift.Transport
import Thrift.Types
import qualified Data.Attoparsec.ByteString as P
import qualified Data.Attoparsec.ByteString.Lazy as LP
import qualified Data.Binary as Binary
import qualified Data.ByteString.Lazy as LBS
import qualified Data.HashMap.Strict as Map
import qualified Data.Text.Lazy as LT
versionMask :: Int32
versionMask = fromIntegral (0xffff0000 :: Word32)
version1 :: Int32
version1 = fromIntegral (0x80010000 :: Word32)
data BinaryProtocol a = Transport a => BinaryProtocol a
getTransport :: Transport t => BinaryProtocol t -> t
getTransport (BinaryProtocol t) = t
instance Transport t => Protocol (BinaryProtocol t) where
readByte p = tReadAll (getTransport p) 1
writeMessage p (n, t, s) f = do
tWrite (getTransport p) messageBegin
f
tFlush $ getTransport p
where
messageBegin = toLazyByteString $
buildBinaryValue (TI32 (version1 .|. fromIntegral (fromEnum t))) <>
buildBinaryValue (TString $ encodeUtf8 n) <>
buildBinaryValue (TI32 s)
readMessage p = (readMessageBegin p >>=)
where
readMessageBegin p = runParser p $ do
TI32 ver <- parseBinaryValue T_I32
if ver .&. versionMask /= version1
then throw $ ProtocolExn PE_BAD_VERSION "Missing version identifier"
else do
TString s <- parseBinaryValue T_STRING
TI32 sz <- parseBinaryValue T_I32
return (decodeUtf8 s, toEnum $ fromIntegral $ ver .&. 0xFF, sz)
writeVal p = tWrite (getTransport p) . toLazyByteString . buildBinaryValue
readVal p = runParser p . parseBinaryValue
instance Transport t => StatelessProtocol (BinaryProtocol t) where
serializeVal _ = toLazyByteString . buildBinaryValue
deserializeVal _ ty bs =
case LP.eitherResult $ LP.parse (parseBinaryValue ty) bs of
Left s -> error s
Right val -> val
buildBinaryValue :: ThriftVal -> Builder
buildBinaryValue (TStruct fields) = buildBinaryStruct fields <> buildType T_STOP
buildBinaryValue (TMap ky vt entries) =
buildType ky <>
buildType vt <>
int32BE (fromIntegral (length entries)) <>
buildBinaryMap entries
buildBinaryValue (TList ty entries) =
buildType ty <>
int32BE (fromIntegral (length entries)) <>
buildBinaryList entries
buildBinaryValue (TSet ty entries) =
buildType ty <>
int32BE (fromIntegral (length entries)) <>
buildBinaryList entries
buildBinaryValue (TBool b) =
word8 $ toEnum $ if b then 1 else 0
buildBinaryValue (TByte b) = int8 b
buildBinaryValue (TI16 i) = int16BE i
buildBinaryValue (TI32 i) = int32BE i
buildBinaryValue (TI64 i) = int64BE i
buildBinaryValue (TDouble d) = doubleBE d
buildBinaryValue (TString s) = int32BE len <> lazyByteString s
where
len :: Int32 = fromIntegral (LBS.length s)
buildBinaryValue (TBinary s) = buildBinaryValue (TString s)
buildBinaryStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder
buildBinaryStruct = Map.foldrWithKey combine mempty
where
combine fid (_,val) s =
buildTypeOf val <> int16BE fid <> buildBinaryValue val <> s
buildBinaryMap :: [(ThriftVal, ThriftVal)] -> Builder
buildBinaryMap = foldl combine mempty
where
combine s (key, val) = s <> buildBinaryValue key <> buildBinaryValue val
buildBinaryList :: [ThriftVal] -> Builder
buildBinaryList = foldr (mappend . buildBinaryValue) mempty
parseBinaryValue :: ThriftType -> P.Parser ThriftVal
parseBinaryValue (T_STRUCT tmap) = TStruct <$> parseBinaryStruct tmap
parseBinaryValue (T_MAP _ _) = do
kt <- parseType
vt <- parseType
n <- Binary.decode . LBS.fromStrict <$> P.take 4
TMap kt vt <$> parseBinaryMap kt vt n
parseBinaryValue (T_LIST _) = do
t <- parseType
n <- Binary.decode . LBS.fromStrict <$> P.take 4
TList t <$> parseBinaryList t n
parseBinaryValue (T_SET _) = do
t <- parseType
n <- Binary.decode . LBS.fromStrict <$> P.take 4
TSet t <$> parseBinaryList t n
parseBinaryValue T_BOOL = TBool . (/=0) <$> P.anyWord8
parseBinaryValue T_BYTE = TByte . Binary.decode . LBS.fromStrict <$> P.take 1
parseBinaryValue T_I16 = TI16 . Binary.decode . LBS.fromStrict <$> P.take 2
parseBinaryValue T_I32 = TI32 . Binary.decode . LBS.fromStrict <$> P.take 4
parseBinaryValue T_I64 = TI64 . Binary.decode . LBS.fromStrict <$> P.take 8
parseBinaryValue T_DOUBLE = TDouble . bsToDouble <$> P.take 8
parseBinaryValue T_STRING = parseBinaryString TString
parseBinaryValue T_BINARY = parseBinaryString TBinary
parseBinaryValue ty = error $ "Cannot read value of type " ++ show ty
parseBinaryString ty = do
i :: Int32 <- Binary.decode . LBS.fromStrict <$> P.take 4
ty . LBS.fromStrict <$> P.take (fromIntegral i)
parseBinaryStruct :: TypeMap -> P.Parser (Map.HashMap Int16 (LT.Text, ThriftVal))
parseBinaryStruct tmap = Map.fromList <$> P.manyTill parseField (matchType T_STOP)
where
parseField = do
t <- parseType
n <- Binary.decode . LBS.fromStrict <$> P.take 2
v <- case (t, Map.lookup n tmap) of
(T_STRING, Just (_, T_BINARY)) -> parseBinaryValue T_BINARY
_ -> parseBinaryValue t
return (n, ("", v))
parseBinaryMap :: ThriftType -> ThriftType -> Int32 -> P.Parser [(ThriftVal, ThriftVal)]
parseBinaryMap kt vt n | n <= 0 = return []
| otherwise = do
k <- parseBinaryValue kt
v <- parseBinaryValue vt
((k,v) :) <$> parseBinaryMap kt vt (n-1)
parseBinaryList :: ThriftType -> Int32 -> P.Parser [ThriftVal]
parseBinaryList ty n | n <= 0 = return []
| otherwise = liftM2 (:) (parseBinaryValue ty)
(parseBinaryList ty (n-1))
buildType :: ThriftType -> Builder
buildType t = word8 $ fromIntegral $ fromEnum t
buildTypeOf :: ThriftVal -> Builder
buildTypeOf = buildType . getTypeOf
parseType :: P.Parser ThriftType
parseType = toEnum . fromIntegral <$> P.anyWord8
matchType :: ThriftType -> P.Parser ThriftType
matchType t = t <$ P.word8 (fromIntegral $ fromEnum t)