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
19 module thrift.protocol.compact;
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;
28 * D implementation of the Compact protocol.
30 * See THRIFT-110 for a protocol description. This implementation is based on
33 final class TCompactProtocol(Transport = TTransport) if (
34 isTTransport!Transport
37 * Constructs a new instance.
40 * trans = The transport to use.
41 * containerSizeLimit = If positive, the container size is limited to the
42 * given number of items.
43 * stringSizeLimit = If positive, the string length is limited to the
44 * given number of bytes.
46 this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
48 this.containerSizeLimit = containerSizeLimit;
49 this.stringSizeLimit = stringSizeLimit;
52 Transport transport() @property {
59 booleanField_ = TField.init;
60 hasBoolValue_ = false;
64 * If positive, limits the number of items of deserialized containers to the
67 * This is useful to avoid allocating excessive amounts of memory when broken
68 * data is received. If the limit is exceeded, a SIZE_LIMIT-type
69 * TProtocolException is thrown.
71 * Defaults to zero (no limit).
73 int containerSizeLimit;
76 * If positive, limits the length of deserialized strings/binary data to the
77 * given number of bytes.
79 * This is useful to avoid allocating excessive amounts of memory when broken
80 * data is received. If the limit is exceeded, a SIZE_LIMIT-type
81 * TProtocolException is thrown.
83 * Defaults to zero (no limit).
91 void writeBool(bool b) {
92 if (booleanField_.name !is null) {
93 // we haven't written the field header yet
94 writeFieldBeginInternal(booleanField_,
95 b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE);
96 booleanField_.name = null;
98 // we're not part of a field, so just write the value
99 writeByte(b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE);
103 void writeByte(byte b) {
104 trans_.write((cast(ubyte*)&b)[0..1]);
107 void writeI16(short i16) {
108 writeVarint32(i32ToZigzag(i16));
111 void writeI32(int i32) {
112 writeVarint32(i32ToZigzag(i32));
115 void writeI64(long i64) {
116 writeVarint64(i64ToZigzag(i64));
119 void writeDouble(double dub) {
120 ulong bits = hostToLe(*cast(ulong*)(&dub));
121 trans_.write((cast(ubyte*)&bits)[0 .. 8]);
124 void writeString(string str) {
125 writeBinary(cast(ubyte[])str);
128 void writeBinary(ubyte[] buf) {
129 assert(buf.length <= int.max);
130 writeVarint32(cast(int)buf.length);
134 void writeMessageBegin(TMessage msg) {
135 writeByte(cast(byte)PROTOCOL_ID);
136 writeByte(cast(byte)((VERSION_N & VERSION_MASK) |
137 ((cast(int)msg.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK)));
138 writeVarint32(msg.seqid);
139 writeString(msg.name);
141 void writeMessageEnd() {}
143 void writeStructBegin(TStruct tstruct) {
144 fieldIdStack_ ~= lastFieldId_;
148 void writeStructEnd() {
149 lastFieldId_ = fieldIdStack_[$ - 1];
150 fieldIdStack_ = fieldIdStack_[0 .. $ - 1];
151 fieldIdStack_.assumeSafeAppend();
154 void writeFieldBegin(TField field) {
155 if (field.type == TType.BOOL) {
156 booleanField_.name = field.name;
157 booleanField_.type = field.type;
158 booleanField_.id = field.id;
160 return writeFieldBeginInternal(field);
163 void writeFieldEnd() {}
165 void writeFieldStop() {
166 writeByte(TType.STOP);
169 void writeListBegin(TList list) {
170 writeCollectionBegin(list.elemType, list.size);
172 void writeListEnd() {}
174 void writeMapBegin(TMap map) {
178 assert(map.size <= int.max);
179 writeVarint32(cast(int)map.size);
180 writeByte(cast(byte)(toCType(map.keyType) << 4 | toCType(map.valueType)));
183 void writeMapEnd() {}
185 void writeSetBegin(TSet set) {
186 writeCollectionBegin(set.elemType, set.size);
188 void writeSetEnd() {}
196 if (hasBoolValue_ == true) {
197 hasBoolValue_ = false;
201 return readByte() == CType.BOOLEAN_TRUE;
207 return cast(byte)b[0];
211 return cast(short)zigzagToI32(readVarint32());
215 return zigzagToI32(readVarint32());
219 return zigzagToI64(readVarint64());
222 double readDouble() {
223 IntBuf!long b = void;
224 trans_.readAll(b.bytes);
225 b.value = leToHost(b.value);
226 return *cast(double*)(&b.value);
229 string readString() {
230 return cast(string)readBinary();
233 ubyte[] readBinary() {
234 auto size = readVarint32();
235 checkSize(size, stringSizeLimit);
241 auto buf = uninitializedArray!(ubyte[])(size);
246 TMessage readMessageBegin() {
249 auto protocolId = readByte();
250 if (protocolId != cast(byte)PROTOCOL_ID) {
251 throw new TProtocolException("Bad protocol identifier",
252 TProtocolException.Type.BAD_VERSION);
255 auto versionAndType = readByte();
256 auto ver = versionAndType & VERSION_MASK;
257 if (ver != VERSION_N) {
258 throw new TProtocolException("Bad protocol version",
259 TProtocolException.Type.BAD_VERSION);
262 msg.type = cast(TMessageType)((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS);
263 msg.seqid = readVarint32();
264 msg.name = readString();
268 void readMessageEnd() {}
270 TStruct readStructBegin() {
271 fieldIdStack_ ~= lastFieldId_;
276 void readStructEnd() {
277 lastFieldId_ = fieldIdStack_[$ - 1];
278 fieldIdStack_ = fieldIdStack_[0 .. $ - 1];
281 TField readFieldBegin() {
285 auto bite = readByte();
286 auto type = cast(CType)(bite & 0x0f);
288 if (type == CType.STOP) {
289 // Struct stop byte, nothing more to do.
295 // Mask off the 4 MSB of the type header, which could contain a field id
297 auto modifier = cast(short)((bite & 0xf0) >> 4);
299 f.id = cast(short)(lastFieldId_ + modifier);
301 // Delta encoding not used, just read the id as usual.
304 f.type = getTType(type);
306 if (type == CType.BOOLEAN_TRUE || type == CType.BOOLEAN_FALSE) {
307 // For boolean fields, the value is encoded in the type – keep it around
308 // for the readBool() call.
309 hasBoolValue_ = true;
310 boolValue_ = (type == CType.BOOLEAN_TRUE ? true : false);
316 void readFieldEnd() {}
318 TList readListBegin() {
319 auto sizeAndType = readByte();
321 auto lsize = (sizeAndType >> 4) & 0xf;
323 lsize = readVarint32();
325 checkSize(lsize, containerSizeLimit);
328 l.elemType = getTType(cast(CType)(sizeAndType & 0x0f));
329 l.size = cast(size_t)lsize;
333 void readListEnd() {}
335 TMap readMapBegin() {
338 auto size = readVarint32();
343 checkSize(size, containerSizeLimit);
346 m.keyType = getTType(cast(CType)(kvType >> 4));
347 m.valueType = getTType(cast(CType)(kvType & 0xf));
353 TSet readSetBegin() {
354 auto sizeAndType = readByte();
356 auto lsize = (sizeAndType >> 4) & 0xf;
358 lsize = readVarint32();
360 checkSize(lsize, containerSizeLimit);
363 s.elemType = getTType(cast(CType)(sizeAndType & 0xf));
364 s.size = cast(size_t)lsize;
371 void writeFieldBeginInternal(TField field, byte typeOverride = -1) {
372 // If there's a type override, use that.
373 auto typeToWrite = (typeOverride == -1 ? toCType(field.type) : typeOverride);
375 // check if we can use delta encoding for the field id
376 if (field.id > lastFieldId_ && (field.id - lastFieldId_) <= 15) {
377 // write them together
378 writeByte(cast(byte)((field.id - lastFieldId_) << 4 | typeToWrite));
380 // write them separate
381 writeByte(cast(byte)typeToWrite);
385 lastFieldId_ = field.id;
389 void writeCollectionBegin(TType elemType, size_t size) {
391 writeByte(cast(byte)(size << 4 | toCType(elemType)));
393 assert(size <= int.max);
394 writeByte(cast(byte)(0xf0 | toCType(elemType)));
395 writeVarint32(cast(int)size);
399 void writeVarint32(uint n) {
404 if ((n & ~0x7F) == 0) {
405 buf[wsize++] = cast(ubyte)n;
408 buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80);
413 trans_.write(buf[0 .. wsize]);
417 * Write an i64 as a varint. Results in 1-10 bytes on the wire.
419 void writeVarint64(ulong n) {
420 ubyte[10] buf = void;
424 if ((n & ~0x7FL) == 0) {
425 buf[wsize++] = cast(ubyte)n;
428 buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80);
433 trans_.write(buf[0 .. wsize]);
437 * Convert l into a zigzag long. This allows negative numbers to be
438 * represented compactly as a varint.
440 ulong i64ToZigzag(long l) {
441 return (l << 1) ^ (l >> 63);
445 * Convert n into a zigzag int. This allows negative numbers to be
446 * represented compactly as a varint.
448 uint i32ToZigzag(int n) {
449 return (n << 1) ^ (n >> 31);
452 CType toCType(TType type) {
453 final switch (type) {
457 return CType.BOOLEAN_TRUE;
479 assert(false, "Invalid type passed.");
484 return cast(int)readVarint64();
487 long readVarint64() {
490 ubyte[10] buf = void; // 64 bits / (7 bits/byte) = 10 bytes.
491 auto bufSize = buf.sizeof;
492 auto borrowed = trans_.borrow(buf.ptr, bufSize);
499 auto bite = borrowed[rsize];
501 val |= cast(ulong)(bite & 0x7f) << shift;
503 if (!(bite & 0x80)) {
504 trans_.consume(rsize);
507 // Have to check for invalid data so we don't crash.
508 if (rsize == buf.sizeof) {
509 throw new TProtocolException(TProtocolException.Type.INVALID_DATA,
510 "Variable-length int over 10 bytes.");
517 trans_.readAll(bite);
520 val |= cast(ulong)(bite[0] & 0x7f) << shift;
522 if (!(bite[0] & 0x80)) {
526 // Might as well check for invalid data on the slow path too.
527 if (rsize >= buf.sizeof) {
528 throw new TProtocolException(TProtocolException.Type.INVALID_DATA,
529 "Variable-length int over 10 bytes.");
536 * Convert from zigzag int to int.
538 int zigzagToI32(uint n) {
539 return (n >> 1) ^ -(n & 1);
543 * Convert from zigzag long to long.
545 long zigzagToI64(ulong n) {
546 return (n >> 1) ^ -(n & 1);
549 TType getTType(CType type) {
550 final switch (type) {
553 case CType.BOOLEAN_FALSE:
555 case CType.BOOLEAN_TRUE:
580 void checkSize(int size, int limit) {
582 throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
583 } else if (limit > 0 && size > limit) {
584 throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
588 enum PROTOCOL_ID = 0x82;
590 enum VERSION_MASK = 0b0001_1111;
591 enum TYPE_MASK = 0b1110_0000;
592 enum TYPE_BITS = 0b0000_0111;
593 enum TYPE_SHIFT_AMOUNT = 5;
595 // Probably need to implement a better stack at some point.
596 short[] fieldIdStack_;
599 TField booleanField_;
608 * TCompactProtocol construction helper to avoid having to explicitly specify
609 * the transport type, i.e. to allow the constructor being called using IFTI
610 * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
611 * enhancement requet 6082)).
613 TCompactProtocol!Transport tCompactProtocol(Transport)(Transport trans,
614 int containerSizeLimit = 0, int stringSizeLimit = 0
615 ) if (isTTransport!Transport)
617 return new TCompactProtocol!Transport(trans,
618 containerSizeLimit, stringSizeLimit);
637 static assert(CType.max <= 0xf,
638 "Compact protocol wire type representation must fit into 4 bits.");
642 import std.exception;
643 import thrift.transport.memory;
645 // Check the message header format.
646 auto buf = new TMemoryBuffer;
647 auto compact = tCompactProtocol(buf);
648 compact.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
650 auto header = new ubyte[7];
654 33, // Version/type byte.
656 3, 102, 111, 111 // Method name.
661 import thrift.internal.test.protocol;
662 testContainerSizeLimit!(TCompactProtocol!())();
663 testStringSizeLimit!(TCompactProtocol!())();
667 * TProtocolFactory creating a TCompactProtocol instance for passed in
670 * The optional Transports template tuple parameter can be used to specify
671 * one or more TTransport implementations to specifically instantiate
672 * TCompactProtocol for. If the actual transport types encountered at
673 * runtime match one of the transports in the list, a specialized protocol
674 * instance is created. Otherwise, a generic TTransport version is used.
676 class TCompactProtocolFactory(Transports...) if (
677 allSatisfy!(isTTransport, Transports)
678 ) : TProtocolFactory {
680 this(int containerSizeLimit = 0, int stringSizeLimit = 0) {
681 containerSizeLimit_ = 0;
682 stringSizeLimit_ = 0;
685 TProtocol getProtocol(TTransport trans) const {
686 foreach (Transport; TypeTuple!(Transports, TTransport)) {
687 auto concreteTrans = cast(Transport)trans;
689 return new TCompactProtocol!Transport(concreteTrans);
692 throw new TProtocolException(
693 "Passed null transport to TCompactProtocolFactory.");
696 int containerSizeLimit_;
697 int stringSizeLimit_;