blob: 7b0acd9d48344635631da214ad84c5a1250057ca [file] [log] [blame]
Bryan Duxbury0781f2b2009-04-07 23:29:42 +00001--
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 Zilbersteinaf5d64a2014-07-31 15:44:13 -070020{-# LANGUAGE CPP #-}
21{-# LANGUAGE ExistentialQuantification #-}
22{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE ScopedTypeVariables #-}
24
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000025module Thrift.Protocol.Binary
26 ( module Thrift.Protocol
27 , BinaryProtocol(..)
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090028 , versionMask
29 , version1
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000030 ) where
31
32import Control.Exception ( throw )
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070033import Control.Monad
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000034import Data.Bits
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070035import Data.ByteString.Lazy.Builder
36import Data.Functor
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000037import Data.Int
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070038import Data.Monoid
Roger Meier6849f202012-05-18 07:35:19 +000039import Data.Text.Lazy.Encoding ( decodeUtf8, encodeUtf8 )
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090040import Data.Word
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000041
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000042import Thrift.Protocol
43import Thrift.Transport
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070044import Thrift.Types
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000045
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070046import qualified Data.Attoparsec.ByteString as P
47import qualified Data.Attoparsec.ByteString.Lazy as LP
48import qualified Data.Binary as Binary
Bryan Duxbury75a33e82010-09-22 00:48:56 +000049import qualified Data.ByteString.Lazy as LBS
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070050import qualified Data.HashMap.Strict as Map
51import qualified Data.Text.Lazy as LT
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000052
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090053versionMask :: Int32
54versionMask = fromIntegral (0xffff0000 :: Word32)
55
56version1 :: Int32
57version1 = fromIntegral (0x80010000 :: Word32)
58
59data BinaryProtocol a = Transport a => BinaryProtocol a
60
61getTransport :: Transport t => BinaryProtocol t -> t
62getTransport (BinaryProtocol t) = t
Bryan Duxburye59a80f2010-09-20 15:21:37 +000063
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070064-- 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 Sukegawa3c420072016-01-24 04:01:27 +090068instance 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 Duxbury0781f2b2009-04-07 23:29:42 +000080
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090081 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 Duxbury0781f2b2009-04-07 23:29:42 +000091
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090092 writeVal p = tWrite (getTransport p) . toLazyByteString . buildBinaryValue
93 readVal p = runParser p . parseBinaryValue
Bryan Duxbury0781f2b2009-04-07 23:29:42 +000094
Nobuaki Sukegawa3c420072016-01-24 04:01:27 +090095instance Transport t => StatelessProtocol (BinaryProtocol t) where
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -070096 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 Duxbury0781f2b2009-04-07 23:29:42 +0000101
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700102-- | Writing Functions
103buildBinaryValue :: ThriftVal -> Builder
104buildBinaryValue (TStruct fields) = buildBinaryStruct fields <> buildType T_STOP
105buildBinaryValue (TMap ky vt entries) =
106 buildType ky <>
107 buildType vt <>
108 int32BE (fromIntegral (length entries)) <>
109 buildBinaryMap entries
110buildBinaryValue (TList ty entries) =
111 buildType ty <>
112 int32BE (fromIntegral (length entries)) <>
113 buildBinaryList entries
114buildBinaryValue (TSet ty entries) =
115 buildType ty <>
116 int32BE (fromIntegral (length entries)) <>
117 buildBinaryList entries
118buildBinaryValue (TBool b) =
119 word8 $ toEnum $ if b then 1 else 0
120buildBinaryValue (TByte b) = int8 b
121buildBinaryValue (TI16 i) = int16BE i
122buildBinaryValue (TI32 i) = int32BE i
123buildBinaryValue (TI64 i) = int64BE i
124buildBinaryValue (TDouble d) = doubleBE d
125buildBinaryValue (TString s) = int32BE len <> lazyByteString s
126 where
127 len :: Int32 = fromIntegral (LBS.length s)
Nobuaki Sukegawae68ccc22015-12-13 21:45:39 +0900128buildBinaryValue (TBinary s) = buildBinaryValue (TString s)
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000129
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700130buildBinaryStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder
131buildBinaryStruct = Map.foldrWithKey combine mempty
132 where
133 combine fid (_,val) s =
134 buildTypeOf val <> int16BE fid <> buildBinaryValue val <> s
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000135
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700136buildBinaryMap :: [(ThriftVal, ThriftVal)] -> Builder
137buildBinaryMap = foldl combine mempty
138 where
139 combine s (key, val) = s <> buildBinaryValue key <> buildBinaryValue val
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000140
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700141buildBinaryList :: [ThriftVal] -> Builder
142buildBinaryList = foldr (mappend . buildBinaryValue) mempty
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000143
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700144-- | Reading Functions
145parseBinaryValue :: ThriftType -> P.Parser ThriftVal
Nobuaki Sukegawae68ccc22015-12-13 21:45:39 +0900146parseBinaryValue (T_STRUCT tmap) = TStruct <$> parseBinaryStruct tmap
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700147parseBinaryValue (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
152parseBinaryValue (T_LIST _) = do
153 t <- parseType
154 n <- Binary.decode . LBS.fromStrict <$> P.take 4
155 TList t <$> parseBinaryList t n
156parseBinaryValue (T_SET _) = do
157 t <- parseType
158 n <- Binary.decode . LBS.fromStrict <$> P.take 4
159 TSet t <$> parseBinaryList t n
160parseBinaryValue T_BOOL = TBool . (/=0) <$> P.anyWord8
161parseBinaryValue T_BYTE = TByte . Binary.decode . LBS.fromStrict <$> P.take 1
162parseBinaryValue T_I16 = TI16 . Binary.decode . LBS.fromStrict <$> P.take 2
163parseBinaryValue T_I32 = TI32 . Binary.decode . LBS.fromStrict <$> P.take 4
164parseBinaryValue T_I64 = TI64 . Binary.decode . LBS.fromStrict <$> P.take 8
165parseBinaryValue T_DOUBLE = TDouble . bsToDouble <$> P.take 8
Nobuaki Sukegawae68ccc22015-12-13 21:45:39 +0900166parseBinaryValue T_STRING = parseBinaryString TString
167parseBinaryValue T_BINARY = parseBinaryString TBinary
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700168parseBinaryValue ty = error $ "Cannot read value of type " ++ show ty
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000169
Nobuaki Sukegawae68ccc22015-12-13 21:45:39 +0900170parseBinaryString ty = do
171 i :: Int32 <- Binary.decode . LBS.fromStrict <$> P.take 4
172 ty . LBS.fromStrict <$> P.take (fromIntegral i)
173
174parseBinaryStruct :: TypeMap -> P.Parser (Map.HashMap Int16 (LT.Text, ThriftVal))
175parseBinaryStruct tmap = Map.fromList <$> P.manyTill parseField (matchType T_STOP)
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700176 where
177 parseField = do
178 t <- parseType
179 n <- Binary.decode . LBS.fromStrict <$> P.take 2
Nobuaki Sukegawae68ccc22015-12-13 21:45:39 +0900180 v <- case (t, Map.lookup n tmap) of
181 (T_STRING, Just (_, T_BINARY)) -> parseBinaryValue T_BINARY
182 _ -> parseBinaryValue t
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700183 return (n, ("", v))
Christian Lavoieae7f7fa2010-11-02 21:42:53 +0000184
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700185parseBinaryMap :: ThriftType -> ThriftType -> Int32 -> P.Parser [(ThriftVal, ThriftVal)]
186parseBinaryMap 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 Lavoieae7f7fa2010-11-02 21:42:53 +0000191
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700192parseBinaryList :: ThriftType -> Int32 -> P.Parser [ThriftVal]
193parseBinaryList ty n | n <= 0 = return []
194 | otherwise = liftM2 (:) (parseBinaryValue ty)
195 (parseBinaryList ty (n-1))
196
Bryan Duxbury0781f2b2009-04-07 23:29:42 +0000197
198
199-- | Write a type as a byte
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700200buildType :: ThriftType -> Builder
201buildType t = word8 $ fromIntegral $ fromEnum t
202
203-- | Write type of a ThriftVal as a byte
204buildTypeOf :: ThriftVal -> Builder
205buildTypeOf = buildType . getTypeOf
Bryan Duxbury0781f2b2009-04-07 23:29:42 +0000206
207-- | Read a byte as though it were a ThriftType
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700208parseType :: P.Parser ThriftType
209parseType = toEnum . fromIntegral <$> P.anyWord8
Bryan Duxbury0781f2b2009-04-07 23:29:42 +0000210
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700211matchType :: ThriftType -> P.Parser ThriftType
212matchType t = t <$ P.word8 (fromIntegral $ fromEnum t)