---
--- Licensed to the Apache Software Foundation (ASF) under one
--- or more contributor license agreements. See the NOTICE file
--- distributed with this work for additional information
--- regarding copyright ownership. The ASF licenses this file
--- to you under the Apache License, Version 2.0 (the
--- "License"); you may not use this file except in compliance
--- with the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing,
--- software distributed under the License is distributed on an
--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
--- KIND, either express or implied. See the License for the
--- specific language governing permissions and limitations
--- under the License.
---
-
-{-# 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
-
--- NOTE: Reading and Writing functions rely on Builders and Data.Binary to
--- encode and decode data. Data.Binary assumes that the binary values it is
--- encoding to and decoding from are in BIG ENDIAN format, and converts the
--- endianness as necessary to match the local machine.
-instance Transport t => Protocol (BinaryProtocol t) where
- readByte p = tReadAll (getTransport p) 1
- -- flushTransport p = tFlush (getTransport p)
- 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
-
--- | Writing Functions
-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
-
--- | Reading Functions
-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))
-
-
-
--- | Write a type as a byte
-buildType :: ThriftType -> Builder
-buildType t = word8 $ fromIntegral $ fromEnum t
-
--- | Write type of a ThriftVal as a byte
-buildTypeOf :: ThriftVal -> Builder
-buildTypeOf = buildType . getTypeOf
-
--- | Read a byte as though it were a ThriftType
-parseType :: P.Parser ThriftType
-parseType = toEnum . fromIntegral <$> P.anyWord8
-
-matchType :: ThriftType -> P.Parser ThriftType
-matchType t = t <$ P.word8 (fromIntegral $ fromEnum t)