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 module thrift.protocol.binary;
20 
21 import std.array : uninitializedArray;
22 import std.typetuple : allSatisfy, TypeTuple;
23 import thrift.protocol.base;
24 import thrift.transport.base;
25 import thrift.internal.endian;
26 
27 /**
28  * TProtocol implementation of the Binary Thrift protocol.
29  */
30 final class TBinaryProtocol(Transport = TTransport) if (
31   isTTransport!Transport
32 ) : TProtocol {
33 
34   /**
35    * Constructs a new instance.
36    *
37    * Params:
38    *   trans = The transport to use.
39    *   containerSizeLimit = If positive, the container size is limited to the
40    *     given number of items.
41    *   stringSizeLimit = If positive, the string length is limited to the
42    *     given number of bytes.
43    *   strictRead = If false, old peers which do not include the protocol
44    *     version are tolerated.
45    *   strictWrite = Whether to include the protocol version in the header.
46    */
47   this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0,
48     bool strictRead = false, bool strictWrite = true
49   ) {
50     trans_ = trans;
51     this.containerSizeLimit = containerSizeLimit;
52     this.stringSizeLimit = stringSizeLimit;
53     this.strictRead = strictRead;
54     this.strictWrite = strictWrite;
55   }
56 
57   Transport transport() @property {
58     return trans_;
59   }
60 
61   void reset() {}
62 
63   /**
64    * If false, old peers which do not include the protocol version in the
65    * message header are tolerated.
66    *
67    * Defaults to false.
68    */
69   bool strictRead;
70 
71   /**
72    * Whether to include the protocol version in the message header (older
73    * versions didn't).
74    *
75    * Defaults to true.
76    */
77   bool strictWrite;
78 
79   /**
80    * If positive, limits the number of items of deserialized containers to the
81    * given amount.
82    *
83    * This is useful to avoid allocating excessive amounts of memory when broken
84    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
85    * TProtocolException is thrown.
86    *
87    * Defaults to zero (no limit).
88    */
89   int containerSizeLimit;
90 
91   /**
92    * If positive, limits the length of deserialized strings/binary data to the
93    * given number of bytes.
94    *
95    * This is useful to avoid allocating excessive amounts of memory when broken
96    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
97    * TProtocolException is thrown.
98    *
99    * Defaults to zero (no limit).
100    */
101   int stringSizeLimit;
102 
103   /*
104    * Writing methods.
105    */
106 
107   void writeBool(bool b) {
108     writeByte(b ? 1 : 0);
109   }
110 
111   void writeByte(byte b) {
112     trans_.write((cast(ubyte*)&b)[0 .. 1]);
113   }
114 
115   void writeI16(short i16) {
116     short net = hostToNet(i16);
117     trans_.write((cast(ubyte*)&net)[0 .. 2]);
118   }
119 
120   void writeI32(int i32) {
121     int net = hostToNet(i32);
122     trans_.write((cast(ubyte*)&net)[0 .. 4]);
123   }
124 
125   void writeI64(long i64) {
126     long net = hostToNet(i64);
127     trans_.write((cast(ubyte*)&net)[0 .. 8]);
128   }
129 
130   void writeDouble(double dub) {
131     static assert(double.sizeof == ulong.sizeof);
132     auto bits = hostToNet(*cast(ulong*)(&dub));
133     trans_.write((cast(ubyte*)&bits)[0 .. 8]);
134   }
135 
136   void writeString(string str) {
137     writeBinary(cast(ubyte[])str);
138   }
139 
140   void writeBinary(ubyte[] buf) {
141     assert(buf.length <= int.max);
142     writeI32(cast(int)buf.length);
143     trans_.write(buf);
144   }
145 
146   void writeMessageBegin(TMessage message) {
147     if (strictWrite) {
148       int versn = VERSION_1 | message.type;
149       writeI32(versn);
150       writeString(message.name);
151       writeI32(message.seqid);
152     } else {
153       writeString(message.name);
154       writeByte(message.type);
155       writeI32(message.seqid);
156     }
157   }
158   void writeMessageEnd() {}
159 
160   void writeStructBegin(TStruct tstruct) {}
161   void writeStructEnd() {}
162 
163   void writeFieldBegin(TField field) {
164     writeByte(field.type);
165     writeI16(field.id);
166   }
167   void writeFieldEnd() {}
168 
169   void writeFieldStop() {
170     writeByte(TType.STOP);
171   }
172 
173   void writeListBegin(TList list) {
174     assert(list.size <= int.max);
175     writeByte(list.elemType);
176     writeI32(cast(int)list.size);
177   }
178   void writeListEnd() {}
179 
180   void writeMapBegin(TMap map) {
181     assert(map.size <= int.max);
182     writeByte(map.keyType);
183     writeByte(map.valueType);
184     writeI32(cast(int)map.size);
185   }
186   void writeMapEnd() {}
187 
188   void writeSetBegin(TSet set) {
189     assert(set.size <= int.max);
190     writeByte(set.elemType);
191     writeI32(cast(int)set.size);
192   }
193   void writeSetEnd() {}
194 
195 
196   /*
197    * Reading methods.
198    */
199 
200   bool readBool() {
201     return readByte() != 0;
202   }
203 
204   byte readByte() {
205     ubyte[1] b = void;
206     trans_.readAll(b);
207     return cast(byte)b[0];
208   }
209 
210   short readI16() {
211     IntBuf!short b = void;
212     trans_.readAll(b.bytes);
213     return netToHost(b.value);
214   }
215 
216   int readI32() {
217     IntBuf!int b = void;
218     trans_.readAll(b.bytes);
219     return netToHost(b.value);
220   }
221 
222   long readI64() {
223     IntBuf!long b = void;
224     trans_.readAll(b.bytes);
225     return netToHost(b.value);
226   }
227 
228   double readDouble() {
229     IntBuf!long b = void;
230     trans_.readAll(b.bytes);
231     b.value = netToHost(b.value);
232     return *cast(double*)(&b.value);
233   }
234 
235   string readString() {
236     return cast(string)readBinary();
237   }
238 
239   ubyte[] readBinary() {
240     return readBinaryBody(readSize(stringSizeLimit));
241   }
242 
243   TMessage readMessageBegin() {
244     TMessage msg = void;
245 
246     int size = readI32();
247     if (size < 0) {
248       int versn = size & VERSION_MASK;
249       if (versn != VERSION_1) {
250         throw new TProtocolException("Bad protocol version.",
251           TProtocolException.Type.BAD_VERSION);
252       }
253 
254       msg.type = cast(TMessageType)(size & MESSAGE_TYPE_MASK);
255       msg.name = readString();
256       msg.seqid = readI32();
257     } else {
258       if (strictRead) {
259         throw new TProtocolException(
260           "Protocol version missing, old client?",
261           TProtocolException.Type.BAD_VERSION);
262       } else {
263         if (size < 0) {
264           throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
265         }
266         msg.name = cast(string)readBinaryBody(size);
267         msg.type = cast(TMessageType)(readByte());
268         msg.seqid = readI32();
269       }
270     }
271 
272     return msg;
273   }
274   void readMessageEnd() {}
275 
276   TStruct readStructBegin() {
277     return TStruct();
278   }
279   void readStructEnd() {}
280 
281   TField readFieldBegin() {
282     TField f = void;
283     f.name = null;
284     f.type = cast(TType)readByte();
285     if (f.type == TType.STOP) return f;
286     f.id = readI16();
287     return f;
288   }
289   void readFieldEnd() {}
290 
291   TList readListBegin() {
292     return TList(cast(TType)readByte(), readSize(containerSizeLimit));
293   }
294   void readListEnd() {}
295 
296   TMap readMapBegin() {
297     return TMap(cast(TType)readByte(), cast(TType)readByte(),
298       readSize(containerSizeLimit));
299   }
300   void readMapEnd() {}
301 
302   TSet readSetBegin() {
303     return TSet(cast(TType)readByte(), readSize(containerSizeLimit));
304   }
305   void readSetEnd() {}
306 
307 private:
308   ubyte[] readBinaryBody(int size) {
309     if (size == 0) {
310       return null;
311     }
312 
313     auto buf = uninitializedArray!(ubyte[])(size);
314     trans_.readAll(buf);
315     return buf;
316   }
317 
318   int readSize(int limit) {
319     auto size = readI32();
320     if (size < 0) {
321       throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
322     } else if (limit > 0 && size > limit) {
323       throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
324     }
325     return size;
326   }
327 
328   enum MESSAGE_TYPE_MASK = 0x000000ff;
329   enum VERSION_MASK = 0xffff0000;
330   enum VERSION_1 = 0x80010000;
331 
332   Transport trans_;
333 }
334 
335 /**
336  * TBinaryProtocol construction helper to avoid having to explicitly specify
337  * the transport type, i.e. to allow the constructor being called using IFTI
338  * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
339  * enhancement requet 6082)).
340  */
341 TBinaryProtocol!Transport tBinaryProtocol(Transport)(Transport trans,
342   int containerSizeLimit = 0, int stringSizeLimit = 0,
343   bool strictRead = false, bool strictWrite = true
344 ) if (isTTransport!Transport) {
345   return new TBinaryProtocol!Transport(trans, containerSizeLimit,
346     stringSizeLimit, strictRead, strictWrite);
347 }
348 
349 unittest {
350   import std.exception;
351   import thrift.transport.memory;
352 
353   // Check the message header format.
354   auto buf = new TMemoryBuffer;
355   auto binary = tBinaryProtocol(buf);
356   binary.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
357 
358   auto header = new ubyte[15];
359   buf.readAll(header);
360   enforce(header == [
361     128, 1, 0, 1, // Version 1, TMessageType.CALL
362     0, 0, 0, 3, // Method name length
363     102, 111, 111, // Method name ("foo")
364     0, 0, 0, 0, // Sequence id
365   ]);
366 }
367 
368 unittest {
369   import thrift.internal.test.protocol;
370   testContainerSizeLimit!(TBinaryProtocol!())();
371   testStringSizeLimit!(TBinaryProtocol!())();
372 }
373 
374 /**
375  * TProtocolFactory creating a TBinaryProtocol instance for passed in
376  * transports.
377  *
378  * The optional Transports template tuple parameter can be used to specify
379  * one or more TTransport implementations to specifically instantiate
380  * TBinaryProtocol for. If the actual transport types encountered at
381  * runtime match one of the transports in the list, a specialized protocol
382  * instance is created. Otherwise, a generic TTransport version is used.
383  */
384 class TBinaryProtocolFactory(Transports...) if (
385   allSatisfy!(isTTransport, Transports)
386 ) : TProtocolFactory {
387   ///
388   this (int containerSizeLimit = 0, int stringSizeLimit = 0,
389     bool strictRead = false, bool strictWrite = true
390   ) {
391     strictRead_ = strictRead;
392     strictWrite_ = strictWrite;
393     containerSizeLimit_ = containerSizeLimit;
394     stringSizeLimit_ = stringSizeLimit;
395   }
396 
397   TProtocol getProtocol(TTransport trans) const {
398     foreach (Transport; TypeTuple!(Transports, TTransport)) {
399       auto concreteTrans = cast(Transport)trans;
400       if (concreteTrans) {
401         return new TBinaryProtocol!Transport(concreteTrans,
402           containerSizeLimit_, stringSizeLimit_, strictRead_, strictWrite_);
403       }
404     }
405     throw new TProtocolException(
406       "Passed null transport to TBinaryProtocolFactoy.");
407   }
408 
409 protected:
410   bool strictRead_;
411   bool strictWrite_;
412   int containerSizeLimit_;
413   int stringSizeLimit_;
414 }