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
10 -- http://www.apache.org/licenses/LICENSE-2.0
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
21 {-# LANGUAGE ExistentialQuantification #-}
22 {-# LANGUAGE OverloadedStrings #-}
23 {-# LANGUAGE ScopedTypeVariables #-}
25 module Thrift.Protocol.Compact
26 ( module Thrift.Protocol
32 import Control.Applicative
34 import Data.Attoparsec.ByteString as P
35 import Data.Attoparsec.ByteString.Lazy as LP
37 import Data.ByteString.Lazy.Builder as B
39 import Data.List as List
42 import Data.Text.Lazy.Encoding ( decodeUtf8, encodeUtf8 )
44 import Thrift.Protocol
45 import Thrift.Transport
48 import qualified Data.ByteString as BS
49 import qualified Data.ByteString.Lazy as LBS
50 import qualified Data.HashMap.Strict as Map
51 import qualified Data.Text.Lazy as LT
53 -- | the Compact Protocol implements the standard Thrift 'TCompactProcotol'
54 -- which is similar to the 'TBinaryProtocol', but takes less space on the wire.
55 -- Integral types are encoded using as varints.
56 data CompactProtocol a = CompactProtocol a
57 -- ^ Constuct a 'CompactProtocol' with a 'Transport'
59 protocolID, version, versionMask, typeMask, typeBits :: Word8
60 protocolID = 0x82 -- 1000 0010
62 versionMask = 0x1f -- 0001 1111
63 typeMask = 0xe0 -- 1110 0000
64 typeBits = 0x07 -- 0000 0111
65 typeShiftAmount :: Int
68 getTransport :: Transport t => CompactProtocol t -> t
69 getTransport (CompactProtocol t) = t
71 instance Transport t => Protocol (CompactProtocol t) where
72 readByte p = tReadAll (getTransport p) 1
73 writeMessage p (n, t, s) f = do
74 tWrite (getTransport p) messageBegin
76 tFlush $ getTransport p
78 messageBegin = toLazyByteString $
80 B.word8 ((version .&. versionMask) .|.
81 (((fromIntegral $ fromEnum t) `shiftL`
82 typeShiftAmount) .&. typeMask)) <>
83 buildVarint (i32ToZigZag s) <>
84 buildCompactValue (TString $ encodeUtf8 n)
86 readMessage p f = readMessageBegin >>= f
88 readMessageBegin = runParser p $ do
89 pid <- fromIntegral <$> P.anyWord8
90 when (pid /= protocolID) $ error "Bad Protocol ID"
91 w <- fromIntegral <$> P.anyWord8
92 let ver = w .&. versionMask
93 when (ver /= version) $ error "Bad Protocol version"
94 let typ = (w `shiftR` typeShiftAmount) .&. typeBits
95 seqId <- parseVarint zigZagToI32
96 TString name <- parseCompactValue T_STRING
97 return (decodeUtf8 name, toEnum $ fromIntegral $ typ, seqId)
99 writeVal p = tWrite (getTransport p) . toLazyByteString . buildCompactValue
100 readVal p ty = runParser p $ parseCompactValue ty
102 instance Transport t => StatelessProtocol (CompactProtocol t) where
103 serializeVal _ = toLazyByteString . buildCompactValue
104 deserializeVal _ ty bs =
105 case LP.eitherResult $ LP.parse (parseCompactValue ty) bs of
109 -- | Writing Functions
110 buildCompactValue :: ThriftVal -> Builder
111 buildCompactValue (TStruct fields) = buildCompactStruct fields
112 buildCompactValue (TMap kt vt entries) =
113 let len = fromIntegral $ length entries :: Word32 in
116 else buildVarint len <>
117 B.word8 (fromTType kt `shiftL` 4 .|. fromTType vt) <>
118 buildCompactMap entries
119 buildCompactValue (TList ty entries) =
120 let len = length entries in
122 then B.word8 $ (fromIntegral len `shiftL` 4) .|. fromTType ty
123 else B.word8 (0xF0 .|. fromTType ty) <>
124 buildVarint (fromIntegral len :: Word32)) <>
125 buildCompactList entries
126 buildCompactValue (TSet ty entries) = buildCompactValue (TList ty entries)
127 buildCompactValue (TBool b) =
128 B.word8 $ toEnum $ if b then 1 else 0
129 buildCompactValue (TByte b) = int8 b
130 buildCompactValue (TI16 i) = buildVarint $ i16ToZigZag i
131 buildCompactValue (TI32 i) = buildVarint $ i32ToZigZag i
132 buildCompactValue (TI64 i) = buildVarint $ i64ToZigZag i
133 buildCompactValue (TDouble d) = doubleLE d
134 buildCompactValue (TString s) = buildVarint len <> lazyByteString s
136 len = fromIntegral (LBS.length s) :: Word32
137 buildCompactValue (TBinary s) = buildCompactValue (TString s)
139 buildCompactStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder
140 buildCompactStruct = flip (loop 0) mempty . Map.toList
142 loop _ [] acc = acc <> B.word8 (fromTType T_STOP)
143 loop lastId ((fid, (_,val)) : fields) acc = loop fid fields $ acc <>
144 (if fid > lastId && fid - lastId <= 15
145 then B.word8 $ fromIntegral ((fid - lastId) `shiftL` 4) .|. typeOf val
146 else B.word8 (typeOf val) <> buildVarint (i16ToZigZag fid)) <>
147 (if typeOf val > 0x02 -- Not a T_BOOL
148 then buildCompactValue val
149 else mempty) -- T_BOOLs are encoded in the type
150 buildCompactMap :: [(ThriftVal, ThriftVal)] -> Builder
151 buildCompactMap = foldl combine mempty
153 combine s (key, val) = buildCompactValue key <> buildCompactValue val <> s
155 buildCompactList :: [ThriftVal] -> Builder
156 buildCompactList = foldr (mappend . buildCompactValue) mempty
158 -- | Reading Functions
159 parseCompactValue :: ThriftType -> Parser ThriftVal
160 parseCompactValue (T_STRUCT tmap) = TStruct <$> parseCompactStruct tmap
161 parseCompactValue (T_MAP kt' vt') = do
164 then return $ TMap kt' vt' []
167 let kt = typeFrom $ w `shiftR` 4
168 vt = typeFrom $ w .&. 0x0F
169 TMap kt vt <$> parseCompactMap kt vt n
170 parseCompactValue (T_LIST ty) = TList ty <$> parseCompactList
171 parseCompactValue (T_SET ty) = TSet ty <$> parseCompactList
172 parseCompactValue T_BOOL = TBool . (/=0) <$> P.anyWord8
173 parseCompactValue T_BYTE = TByte . fromIntegral <$> P.anyWord8
174 parseCompactValue T_I16 = TI16 <$> parseVarint zigZagToI16
175 parseCompactValue T_I32 = TI32 <$> parseVarint zigZagToI32
176 parseCompactValue T_I64 = TI64 <$> parseVarint zigZagToI64
177 parseCompactValue T_DOUBLE = TDouble . bsToDoubleLE <$> P.take 8
178 parseCompactValue T_STRING = parseCompactString TString
179 parseCompactValue T_BINARY = parseCompactString TBinary
180 parseCompactValue ty = error $ "Cannot read value of type " ++ show ty
182 parseCompactString ty = do
183 len :: Word32 <- parseVarint id
184 ty . LBS.fromStrict <$> P.take (fromIntegral len)
186 parseCompactStruct :: TypeMap -> Parser (Map.HashMap Int16 (LT.Text, ThriftVal))
187 parseCompactStruct tmap = Map.fromList <$> parseFields 0
189 parseFields :: Int16 -> Parser [(Int16, (LT.Text, ThriftVal))]
190 parseFields lastId = do
195 let ty = typeFrom (w .&. 0x0F)
196 modifier = (w .&. 0xF0) `shiftR` 4
197 fid <- if modifier /= 0
198 then return (lastId + fromIntegral modifier)
199 else parseVarint zigZagToI16
200 val <- if ty == T_BOOL
201 then return (TBool $ (w .&. 0x0F) == 0x01)
202 else case (ty, Map.lookup fid tmap) of
203 (T_STRING, Just (_, T_BINARY)) -> parseCompactValue T_BINARY
204 _ -> parseCompactValue ty
205 ((fid, (LT.empty, val)) : ) <$> parseFields fid
207 parseCompactMap :: ThriftType -> ThriftType -> Int32 ->
208 Parser [(ThriftVal, ThriftVal)]
209 parseCompactMap kt vt n | n <= 0 = return []
211 k <- parseCompactValue kt
212 v <- parseCompactValue vt
213 ((k,v) :) <$> parseCompactMap kt vt (n-1)
215 parseCompactList :: Parser [ThriftVal]
216 parseCompactList = do
218 let ty = typeFrom $ w .&. 0x0F
220 size <- if lsize == 0xF
222 else return $ fromIntegral lsize
225 loop :: ThriftType -> Int32 -> Parser [ThriftVal]
226 loop ty n | n <= 0 = return []
227 | otherwise = liftM2 (:) (parseCompactValue ty)
230 -- Signed numbers must be converted to "Zig Zag" format before they can be
231 -- serialized in the Varint format
232 i16ToZigZag :: Int16 -> Word16
233 i16ToZigZag n = fromIntegral $ (n `shiftL` 1) `xor` (n `shiftR` 15)
235 zigZagToI16 :: Word16 -> Int16
236 zigZagToI16 n = fromIntegral $ (n `shiftR` 1) `xor` negate (n .&. 0x1)
238 i32ToZigZag :: Int32 -> Word32
239 i32ToZigZag n = fromIntegral $ (n `shiftL` 1) `xor` (n `shiftR` 31)
241 zigZagToI32 :: Word32 -> Int32
242 zigZagToI32 n = fromIntegral $ (n `shiftR` 1) `xor` negate (n .&. 0x1)
244 i64ToZigZag :: Int64 -> Word64
245 i64ToZigZag n = fromIntegral $ (n `shiftL` 1) `xor` (n `shiftR` 63)
247 zigZagToI64 :: Word64 -> Int64
248 zigZagToI64 n = fromIntegral $ (n `shiftR` 1) `xor` negate (n .&. 0x1)
250 buildVarint :: (Bits a, Integral a) => a -> Builder
251 buildVarint n | n .&. complement 0x7F == 0 = B.word8 $ fromIntegral n
252 | otherwise = B.word8 (0x80 .|. (fromIntegral n .&. 0x7F)) <>
253 buildVarint (n `shiftR` 7)
255 parseVarint :: (Bits a, Integral a, Ord a) => (a -> b) -> Parser b
256 parseVarint fromZigZag = do
257 bytestemp <- BS.unpack <$> P.takeTill (not . flip testBit 7)
259 let bytes = lsb : List.reverse bytestemp
260 return $ fromZigZag $ List.foldl' combine 0x00 bytes
261 where combine a b = (a `shiftL` 7) .|. (fromIntegral b .&. 0x7f)
263 -- | Compute the Compact Type
264 fromTType :: ThriftType -> Word8
265 fromTType ty = case ty of
279 T_VOID -> error "No Compact type for T_VOID"
281 typeOf :: ThriftVal -> Word8
297 typeFrom :: Word8 -> ThriftType
298 typeFrom w = case w of
307 0x09 -> T_LIST T_VOID
309 0x0B -> T_MAP T_VOID T_VOID
310 0x0C -> T_STRUCT Map.empty
311 n -> error $ "typeFrom: " ++ show n ++ " is not a compact type"