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(..) |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 28 | , versionMask |
| 29 | , version1 |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 30 | ) where |
| 31 | |
| 32 | import Control.Exception ( throw ) |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 33 | import Control.Monad |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 34 | import Data.Bits |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 35 | import Data.ByteString.Lazy.Builder |
| 36 | import Data.Functor |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 37 | import Data.Int |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 38 | import Data.Monoid |
Roger Meier | 6849f20 | 2012-05-18 07:35:19 +0000 | [diff] [blame] | 39 | import Data.Text.Lazy.Encoding ( decodeUtf8, encodeUtf8 ) |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 40 | import Data.Word |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 41 | |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 42 | import Thrift.Protocol |
| 43 | import Thrift.Transport |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 44 | import Thrift.Types |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 45 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 46 | import qualified Data.Attoparsec.ByteString as P |
| 47 | import qualified Data.Attoparsec.ByteString.Lazy as LP |
| 48 | import qualified Data.Binary as Binary |
Bryan Duxbury | 75a33e8 | 2010-09-22 00:48:56 +0000 | [diff] [blame] | 49 | import qualified Data.ByteString.Lazy as LBS |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 50 | import qualified Data.HashMap.Strict as Map |
| 51 | import qualified Data.Text.Lazy as LT |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 52 | |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 53 | versionMask :: Int32 |
| 54 | versionMask = fromIntegral (0xffff0000 :: Word32) |
| 55 | |
| 56 | version1 :: Int32 |
| 57 | version1 = fromIntegral (0x80010000 :: Word32) |
| 58 | |
| 59 | data BinaryProtocol a = Transport a => BinaryProtocol a |
| 60 | |
| 61 | getTransport :: Transport t => BinaryProtocol t -> t |
| 62 | getTransport (BinaryProtocol t) = t |
Bryan Duxbury | e59a80f | 2010-09-20 15:21:37 +0000 | [diff] [blame] | 63 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 64 | -- NOTE: Reading and Writing functions rely on Builders and Data.Binary to |
| 65 | -- encode and decode data. Data.Binary assumes that the binary values it is |
| 66 | -- encoding to and decoding from are in BIG ENDIAN format, and converts the |
| 67 | -- endianness as necessary to match the local machine. |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 68 | instance Transport t => Protocol (BinaryProtocol t) where |
| 69 | readByte p = tReadAll (getTransport p) 1 |
| 70 | -- flushTransport p = tFlush (getTransport p) |
| 71 | writeMessage p (n, t, s) f = do |
| 72 | tWrite (getTransport p) messageBegin |
| 73 | f |
| 74 | tFlush $ getTransport p |
| 75 | where |
| 76 | messageBegin = toLazyByteString $ |
| 77 | buildBinaryValue (TI32 (version1 .|. fromIntegral (fromEnum t))) <> |
| 78 | buildBinaryValue (TString $ encodeUtf8 n) <> |
| 79 | buildBinaryValue (TI32 s) |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 80 | |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 81 | readMessage p = (readMessageBegin p >>=) |
| 82 | where |
| 83 | readMessageBegin p = runParser p $ do |
| 84 | TI32 ver <- parseBinaryValue T_I32 |
| 85 | if ver .&. versionMask /= version1 |
| 86 | then throw $ ProtocolExn PE_BAD_VERSION "Missing version identifier" |
| 87 | else do |
| 88 | TString s <- parseBinaryValue T_STRING |
| 89 | TI32 sz <- parseBinaryValue T_I32 |
| 90 | return (decodeUtf8 s, toEnum $ fromIntegral $ ver .&. 0xFF, sz) |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 91 | |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 92 | writeVal p = tWrite (getTransport p) . toLazyByteString . buildBinaryValue |
| 93 | readVal p = runParser p . parseBinaryValue |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 94 | |
Nobuaki Sukegawa | 3c42007 | 2016-01-24 04:01:27 +0900 | [diff] [blame] | 95 | instance Transport t => StatelessProtocol (BinaryProtocol t) where |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 96 | serializeVal _ = toLazyByteString . buildBinaryValue |
| 97 | deserializeVal _ ty bs = |
| 98 | case LP.eitherResult $ LP.parse (parseBinaryValue ty) bs of |
| 99 | Left s -> error s |
| 100 | Right val -> val |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 101 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 102 | -- | Writing Functions |
| 103 | buildBinaryValue :: ThriftVal -> Builder |
| 104 | buildBinaryValue (TStruct fields) = buildBinaryStruct fields <> buildType T_STOP |
| 105 | buildBinaryValue (TMap ky vt entries) = |
| 106 | buildType ky <> |
| 107 | buildType vt <> |
| 108 | int32BE (fromIntegral (length entries)) <> |
| 109 | buildBinaryMap entries |
| 110 | buildBinaryValue (TList ty entries) = |
| 111 | buildType ty <> |
| 112 | int32BE (fromIntegral (length entries)) <> |
| 113 | buildBinaryList entries |
| 114 | buildBinaryValue (TSet ty entries) = |
| 115 | buildType ty <> |
| 116 | int32BE (fromIntegral (length entries)) <> |
| 117 | buildBinaryList entries |
| 118 | buildBinaryValue (TBool b) = |
| 119 | word8 $ toEnum $ if b then 1 else 0 |
| 120 | buildBinaryValue (TByte b) = int8 b |
| 121 | buildBinaryValue (TI16 i) = int16BE i |
| 122 | buildBinaryValue (TI32 i) = int32BE i |
| 123 | buildBinaryValue (TI64 i) = int64BE i |
| 124 | buildBinaryValue (TDouble d) = doubleBE d |
| 125 | buildBinaryValue (TString s) = int32BE len <> lazyByteString s |
| 126 | where |
| 127 | len :: Int32 = fromIntegral (LBS.length s) |
Nobuaki Sukegawa | e68ccc2 | 2015-12-13 21:45:39 +0900 | [diff] [blame] | 128 | buildBinaryValue (TBinary s) = buildBinaryValue (TString s) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 129 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 130 | buildBinaryStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder |
| 131 | buildBinaryStruct = Map.foldrWithKey combine mempty |
| 132 | where |
| 133 | combine fid (_,val) s = |
| 134 | buildTypeOf val <> int16BE fid <> buildBinaryValue val <> s |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 135 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 136 | buildBinaryMap :: [(ThriftVal, ThriftVal)] -> Builder |
| 137 | buildBinaryMap = foldl combine mempty |
| 138 | where |
| 139 | combine s (key, val) = s <> buildBinaryValue key <> buildBinaryValue val |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 140 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 141 | buildBinaryList :: [ThriftVal] -> Builder |
| 142 | buildBinaryList = foldr (mappend . buildBinaryValue) mempty |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 143 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 144 | -- | Reading Functions |
| 145 | parseBinaryValue :: ThriftType -> P.Parser ThriftVal |
Nobuaki Sukegawa | e68ccc2 | 2015-12-13 21:45:39 +0900 | [diff] [blame] | 146 | parseBinaryValue (T_STRUCT tmap) = TStruct <$> parseBinaryStruct tmap |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 147 | parseBinaryValue (T_MAP _ _) = do |
| 148 | kt <- parseType |
| 149 | vt <- parseType |
| 150 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 151 | TMap kt vt <$> parseBinaryMap kt vt n |
| 152 | parseBinaryValue (T_LIST _) = do |
| 153 | t <- parseType |
| 154 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 155 | TList t <$> parseBinaryList t n |
| 156 | parseBinaryValue (T_SET _) = do |
| 157 | t <- parseType |
| 158 | n <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 159 | TSet t <$> parseBinaryList t n |
| 160 | parseBinaryValue T_BOOL = TBool . (/=0) <$> P.anyWord8 |
| 161 | parseBinaryValue T_BYTE = TByte . Binary.decode . LBS.fromStrict <$> P.take 1 |
| 162 | parseBinaryValue T_I16 = TI16 . Binary.decode . LBS.fromStrict <$> P.take 2 |
| 163 | parseBinaryValue T_I32 = TI32 . Binary.decode . LBS.fromStrict <$> P.take 4 |
| 164 | parseBinaryValue T_I64 = TI64 . Binary.decode . LBS.fromStrict <$> P.take 8 |
| 165 | parseBinaryValue T_DOUBLE = TDouble . bsToDouble <$> P.take 8 |
Nobuaki Sukegawa | e68ccc2 | 2015-12-13 21:45:39 +0900 | [diff] [blame] | 166 | parseBinaryValue T_STRING = parseBinaryString TString |
| 167 | parseBinaryValue T_BINARY = parseBinaryString TBinary |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 168 | parseBinaryValue ty = error $ "Cannot read value of type " ++ show ty |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 169 | |
Nobuaki Sukegawa | e68ccc2 | 2015-12-13 21:45:39 +0900 | [diff] [blame] | 170 | parseBinaryString ty = do |
| 171 | i :: Int32 <- Binary.decode . LBS.fromStrict <$> P.take 4 |
| 172 | ty . LBS.fromStrict <$> P.take (fromIntegral i) |
| 173 | |
| 174 | parseBinaryStruct :: TypeMap -> P.Parser (Map.HashMap Int16 (LT.Text, ThriftVal)) |
| 175 | parseBinaryStruct tmap = Map.fromList <$> P.manyTill parseField (matchType T_STOP) |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 176 | where |
| 177 | parseField = do |
| 178 | t <- parseType |
| 179 | n <- Binary.decode . LBS.fromStrict <$> P.take 2 |
Nobuaki Sukegawa | e68ccc2 | 2015-12-13 21:45:39 +0900 | [diff] [blame] | 180 | v <- case (t, Map.lookup n tmap) of |
| 181 | (T_STRING, Just (_, T_BINARY)) -> parseBinaryValue T_BINARY |
| 182 | _ -> parseBinaryValue t |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 183 | return (n, ("", v)) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 184 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 185 | parseBinaryMap :: ThriftType -> ThriftType -> Int32 -> P.Parser [(ThriftVal, ThriftVal)] |
| 186 | parseBinaryMap kt vt n | n <= 0 = return [] |
| 187 | | otherwise = do |
| 188 | k <- parseBinaryValue kt |
| 189 | v <- parseBinaryValue vt |
| 190 | ((k,v) :) <$> parseBinaryMap kt vt (n-1) |
Christian Lavoie | ae7f7fa | 2010-11-02 21:42:53 +0000 | [diff] [blame] | 191 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 192 | parseBinaryList :: ThriftType -> Int32 -> P.Parser [ThriftVal] |
| 193 | parseBinaryList ty n | n <= 0 = return [] |
| 194 | | otherwise = liftM2 (:) (parseBinaryValue ty) |
| 195 | (parseBinaryList ty (n-1)) |
| 196 | |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 197 | |
| 198 | |
| 199 | -- | Write a type as a byte |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 200 | buildType :: ThriftType -> Builder |
| 201 | buildType t = word8 $ fromIntegral $ fromEnum t |
| 202 | |
| 203 | -- | Write type of a ThriftVal as a byte |
| 204 | buildTypeOf :: ThriftVal -> Builder |
| 205 | buildTypeOf = buildType . getTypeOf |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 206 | |
| 207 | -- | Read a byte as though it were a ThriftType |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 208 | parseType :: P.Parser ThriftType |
| 209 | parseType = toEnum . fromIntegral <$> P.anyWord8 |
Bryan Duxbury | 0781f2b | 2009-04-07 23:29:42 +0000 | [diff] [blame] | 210 | |
Noam Zilberstein | af5d64a | 2014-07-31 15:44:13 -0700 | [diff] [blame] | 211 | matchType :: ThriftType -> P.Parser ThriftType |
| 212 | matchType t = t <$ P.word8 (fromIntegral $ fromEnum t) |