]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
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 | } |