Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 1 | -- |
| 2 | -- Licensed to the Apache Software Foundation (ASF) under one |
| 3 | -- or more contributor license agreements. See the NOTICE file |
| 4 | -- distributed with this work for additional information |
| 5 | -- regarding copyright ownership. The ASF licenses this file |
| 6 | -- to you under the Apache License, Version 2.0 (the |
| 7 | -- "License"); you may not use this file except in compliance |
| 8 | -- with the License. You may obtain a copy of the License at |
| 9 | -- |
| 10 | -- http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | -- |
| 12 | -- Unless required by applicable law or agreed to in writing, |
| 13 | -- software distributed under the License is distributed on an |
| 14 | -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | -- KIND, either express or implied. See the License for the |
| 16 | -- specific language governing permissions and limitations |
| 17 | -- under the License. |
| 18 | -- |
| 19 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 20 | {-# LANGUAGE CPP #-} |
| 21 | {-# LANGUAGE ExistentialQuantification #-} |
| 22 | {-# LANGUAGE OverloadedStrings #-} |
| 23 | {-# LANGUAGE ScopedTypeVariables #-} |
| 24 | |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 25 | module Thrift.Protocol.Binary |
| 26 | ( module Thrift.Protocol |
| 27 | , BinaryProtocol(..) |
| 28 | ) where |
| 29 | |
| 30 | import Control.Exception ( throw ) |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 31 | import Control.Monad |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 32 | import Data.Bits |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 33 | import Data.ByteString.Lazy.Builder |
| 34 | import Data.Functor |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 35 | import Data.Int |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 36 | import Data.Monoid |
Roger Meier | 6849f20 | 2012-05-18 07:35:19 +0000 | [diff] [blame] | 37 | import Data.Text.Lazy.Encoding ( decodeUtf8, encodeUtf8 ) |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 38 | |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 39 | import Thrift.Protocol |
| 40 | import Thrift.Transport |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 41 | import Thrift.Types |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 42 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 43 | import qualified Data.Attoparsec.ByteString as P |
| 44 | import qualified Data.Attoparsec.ByteString.Lazy as LP |
| 45 | import qualified Data.Binary as Binary |
Bryan Duxbury | 75a33e8 | 2010-09-22 00:48:56 +0000 | [diff] [blame] | 46 | import qualified Data.ByteString.Lazy as LBS |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 47 | import qualified Data.HashMap.Strict as Map |
| 48 | import qualified Data.Text.Lazy as LT |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 49 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 50 | data BinaryProtocol a = BinaryProtocol a |
Bryan Duxbury | e59a80f | 2010-09-20 15:21:37 +0000 | [diff] [blame] | 51 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 52 | -- NOTE: Reading and Writing functions rely on Builders and Data.Binary to |
| 53 | -- encode and decode data. Data.Binary assumes that the binary values it is |
| 54 | -- encoding to and decoding from are in BIG ENDIAN format, and converts the |
| 55 | -- endianness as necessary to match the local machine. |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 56 | instance Protocol BinaryProtocol where |
| 57 | getTransport (BinaryProtocol t) = t |
| 58 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 59 | writeMessageBegin p (n, t, s) = tWrite (getTransport p) $ toLazyByteString $ |
| 60 | buildBinaryValue (TI32 (version1 .|. fromIntegral (fromEnum t))) <> |
| 61 | buildBinaryValue (TString $ encodeUtf8 n) <> |
| 62 | buildBinaryValue (TI32 s) |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 63 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 64 | readMessageBegin p = runParser p $ do |
| 65 | TI32 ver <- parseBinaryValue T_I32 |
| 66 | if ver .&. versionMask /= version1 |
| 67 | then throw $ ProtocolExn PE_BAD_VERSION "Missing version identifier" |
| 68 | else do |
| 69 | TString s <- parseBinaryValue T_STRING |
| 70 | TI32 sz <- parseBinaryValue T_I32 |
| 71 | return (decodeUtf8 s, toEnum $ fromIntegral $ ver .&. 0xFF, sz) |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 72 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 73 | serializeVal _ = toLazyByteString . buildBinaryValue |
| 74 | deserializeVal _ ty bs = |
| 75 | case LP.eitherResult $ LP.parse (parseBinaryValue ty) bs of |
| 76 | Left s -> error s |
| 77 | Right val -> val |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 78 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 79 | readVal p = runParser p . parseBinaryValue |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 80 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 81 | -- | Writing Functions |
| 82 | buildBinaryValue :: ThriftVal -> Builder |
| 83 | buildBinaryValue (TStruct fields) = buildBinaryStruct fields <> buildType T_STOP |
| 84 | buildBinaryValue (TMap ky vt entries) = |
| 85 | buildType ky <> |
| 86 | buildType vt <> |
| 87 | int32BE (fromIntegral (length entries)) <> |
| 88 | buildBinaryMap entries |
| 89 | buildBinaryValue (TList ty entries) = |
| 90 | buildType ty <> |
| 91 | int32BE (fromIntegral (length entries)) <> |
| 92 | buildBinaryList entries |
| 93 | buildBinaryValue (TSet ty entries) = |
| 94 | buildType ty <> |
| 95 | int32BE (fromIntegral (length entries)) <> |
| 96 | buildBinaryList entries |
| 97 | buildBinaryValue (TBool b) = |
| 98 | word8 $ toEnum $ if b then 1 else 0 |
| 99 | buildBinaryValue (TByte b) = int8 b |
| 100 | buildBinaryValue (TI16 i) = int16BE i |
| 101 | buildBinaryValue (TI32 i) = int32BE i |
| 102 | buildBinaryValue (TI64 i) = int64BE i |
| 103 | buildBinaryValue (TDouble d) = doubleBE d |
| 104 | buildBinaryValue (TString s) = int32BE len <> lazyByteString s |
| 105 | where |
| 106 | len :: Int32 = fromIntegral (LBS.length s) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 107 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 108 | buildBinaryStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder |
| 109 | buildBinaryStruct = Map.foldrWithKey combine mempty |
| 110 | where |
| 111 | combine fid (_,val) s = |
| 112 | buildTypeOf val <> int16BE fid <> buildBinaryValue val <> s |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 113 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 114 | buildBinaryMap :: [(ThriftVal, ThriftVal)] -> Builder |
| 115 | buildBinaryMap = foldl combine mempty |
| 116 | where |
| 117 | combine s (key, val) = s <> buildBinaryValue key <> buildBinaryValue val |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 118 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 119 | buildBinaryList :: [ThriftVal] -> Builder |
| 120 | buildBinaryList = foldr (mappend . buildBinaryValue) mempty |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 121 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 122 | -- | Reading Functions |
| 123 | parseBinaryValue :: ThriftType -> P.Parser ThriftVal |
| 124 | parseBinaryValue (T_STRUCT _) = TStruct <$> parseBinaryStruct |
| 125 | parseBinaryValue (T_MAP _ _) = do |
| 126 | kt <- parseType |
| 127 | vt <- parseType |
| 128 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 129 | TMap kt vt <$> parseBinaryMap kt vt n |
| 130 | parseBinaryValue (T_LIST _) = do |
| 131 | t <- parseType |
| 132 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 133 | TList t <$> parseBinaryList t n |
| 134 | parseBinaryValue (T_SET _) = do |
| 135 | t <- parseType |
| 136 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 137 | TSet t <$> parseBinaryList t n |
| 138 | parseBinaryValue T_BOOL = TBool . (/=0) <$> P.anyWord8 |
| 139 | parseBinaryValue T_BYTE = TByte . Binary.decode . LBS.fromStrict <$> P.take 1 |
| 140 | parseBinaryValue T_I16 = TI16 . Binary.decode . LBS.fromStrict <$> P.take 2 |
| 141 | parseBinaryValue T_I32 = TI32 . Binary.decode . LBS.fromStrict <$> P.take 4 |
| 142 | parseBinaryValue T_I64 = TI64 . Binary.decode . LBS.fromStrict <$> P.take 8 |
| 143 | parseBinaryValue T_DOUBLE = TDouble . bsToDouble <$> P.take 8 |
| 144 | parseBinaryValue T_STRING = do |
| 145 | i :: Int32 <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 146 | TString . LBS.fromStrict <$> P.take (fromIntegral i) |
| 147 | parseBinaryValue ty = error $ "Cannot read value of type " ++ show ty |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 148 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 149 | parseBinaryStruct :: P.Parser (Map.HashMap Int16 (LT.Text, ThriftVal)) |
| 150 | parseBinaryStruct = Map.fromList <$> P.manyTill parseField (matchType T_STOP) |
| 151 | where |
| 152 | parseField = do |
| 153 | t <- parseType |
| 154 | n <- Binary.decode . LBS.fromStrict <$> P.take 2 |
| 155 | v <- parseBinaryValue t |
| 156 | return (n, ("", v)) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 157 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 158 | parseBinaryMap :: ThriftType -> ThriftType -> Int32 -> P.Parser [(ThriftVal, ThriftVal)] |
| 159 | parseBinaryMap kt vt n | n <= 0 = return [] |
| 160 | | otherwise = do |
| 161 | k <- parseBinaryValue kt |
| 162 | v <- parseBinaryValue vt |
| 163 | ((k,v) :) <$> parseBinaryMap kt vt (n-1) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 164 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 165 | parseBinaryList :: ThriftType -> Int32 -> P.Parser [ThriftVal] |
| 166 | parseBinaryList ty n | n <= 0 = return [] |
| 167 | | otherwise = liftM2 (:) (parseBinaryValue ty) |
| 168 | (parseBinaryList ty (n-1)) |
| 169 | |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 170 | |
| 171 | |
| 172 | -- | Write a type as a byte |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 173 | buildType :: ThriftType -> Builder |
| 174 | buildType t = word8 $ fromIntegral $ fromEnum t |
| 175 | |
| 176 | -- | Write type of a ThriftVal as a byte |
| 177 | buildTypeOf :: ThriftVal -> Builder |
| 178 | buildTypeOf = buildType . getTypeOf |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 179 | |
| 180 | -- | Read a byte as though it were a ThriftType |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 181 | parseType :: P.Parser ThriftType |
| 182 | parseType = toEnum . fromIntegral <$> P.anyWord8 |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 183 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 184 | matchType :: ThriftType -> P.Parser ThriftType |
| 185 | matchType t = t <$ P.word8 (fromIntegral $ fromEnum t) |