| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| module thrift.protocol.binary; |
| |
| import std.array : uninitializedArray; |
| import std.typetuple : allSatisfy, TypeTuple; |
| import thrift.protocol.base; |
| import thrift.transport.base; |
| import thrift.internal.endian; |
| |
| /** |
| * TProtocol implementation of the Binary Thrift protocol. |
| */ |
| final class TBinaryProtocol(Transport = TTransport) if ( |
| isTTransport!Transport |
| ) : TProtocol { |
| |
| /** |
| * Constructs a new instance. |
| * |
| * Params: |
| * trans = The transport to use. |
| * containerSizeLimit = If positive, the container size is limited to the |
| * given number of items. |
| * stringSizeLimit = If positive, the string length is limited to the |
| * given number of bytes. |
| * strictRead = If false, old peers which do not include the protocol |
| * version are tolerated. |
| * strictWrite = Whether to include the protocol version in the header. |
| */ |
| this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0, |
| bool strictRead = false, bool strictWrite = true |
| ) { |
| trans_ = trans; |
| this.containerSizeLimit = containerSizeLimit; |
| this.stringSizeLimit = stringSizeLimit; |
| this.strictRead = strictRead; |
| this.strictWrite = strictWrite; |
| } |
| |
| Transport transport() @property { |
| return trans_; |
| } |
| |
| void reset() {} |
| |
| /** |
| * If false, old peers which do not include the protocol version in the |
| * message header are tolerated. |
| * |
| * Defaults to false. |
| */ |
| bool strictRead; |
| |
| /** |
| * Whether to include the protocol version in the message header (older |
| * versions didn't). |
| * |
| * Defaults to true. |
| */ |
| bool strictWrite; |
| |
| /** |
| * If positive, limits the number of items of deserialized containers to the |
| * given amount. |
| * |
| * This is useful to avoid allocating excessive amounts of memory when broken |
| * data is received. If the limit is exceeded, a SIZE_LIMIT-type |
| * TProtocolException is thrown. |
| * |
| * Defaults to zero (no limit). |
| */ |
| int containerSizeLimit; |
| |
| /** |
| * If positive, limits the length of deserialized strings/binary data to the |
| * given number of bytes. |
| * |
| * This is useful to avoid allocating excessive amounts of memory when broken |
| * data is received. If the limit is exceeded, a SIZE_LIMIT-type |
| * TProtocolException is thrown. |
| * |
| * Defaults to zero (no limit). |
| */ |
| int stringSizeLimit; |
| |
| /* |
| * Writing methods. |
| */ |
| |
| void writeBool(bool b) { |
| writeByte(b ? 1 : 0); |
| } |
| |
| void writeByte(byte b) { |
| trans_.write((cast(ubyte*)&b)[0 .. 1]); |
| } |
| |
| void writeI16(short i16) { |
| short net = hostToNet(i16); |
| trans_.write((cast(ubyte*)&net)[0 .. 2]); |
| } |
| |
| void writeI32(int i32) { |
| int net = hostToNet(i32); |
| trans_.write((cast(ubyte*)&net)[0 .. 4]); |
| } |
| |
| void writeI64(long i64) { |
| long net = hostToNet(i64); |
| trans_.write((cast(ubyte*)&net)[0 .. 8]); |
| } |
| |
| void writeDouble(double dub) { |
| static assert(double.sizeof == ulong.sizeof); |
| auto bits = hostToNet(*cast(ulong*)(&dub)); |
| trans_.write((cast(ubyte*)&bits)[0 .. 8]); |
| } |
| |
| void writeString(string str) { |
| writeBinary(cast(ubyte[])str); |
| } |
| |
| void writeBinary(ubyte[] buf) { |
| assert(buf.length <= int.max); |
| writeI32(cast(int)buf.length); |
| trans_.write(buf); |
| } |
| |
| void writeMessageBegin(TMessage message) { |
| if (strictWrite) { |
| int versn = VERSION_1 | message.type; |
| writeI32(versn); |
| writeString(message.name); |
| writeI32(message.seqid); |
| } else { |
| writeString(message.name); |
| writeByte(message.type); |
| writeI32(message.seqid); |
| } |
| } |
| void writeMessageEnd() {} |
| |
| void writeStructBegin(TStruct tstruct) {} |
| void writeStructEnd() {} |
| |
| void writeFieldBegin(TField field) { |
| writeByte(field.type); |
| writeI16(field.id); |
| } |
| void writeFieldEnd() {} |
| |
| void writeFieldStop() { |
| writeByte(TType.STOP); |
| } |
| |
| void writeListBegin(TList list) { |
| assert(list.size <= int.max); |
| writeByte(list.elemType); |
| writeI32(cast(int)list.size); |
| } |
| void writeListEnd() {} |
| |
| void writeMapBegin(TMap map) { |
| assert(map.size <= int.max); |
| writeByte(map.keyType); |
| writeByte(map.valueType); |
| writeI32(cast(int)map.size); |
| } |
| void writeMapEnd() {} |
| |
| void writeSetBegin(TSet set) { |
| assert(set.size <= int.max); |
| writeByte(set.elemType); |
| writeI32(cast(int)set.size); |
| } |
| void writeSetEnd() {} |
| |
| |
| /* |
| * Reading methods. |
| */ |
| |
| bool readBool() { |
| return readByte() != 0; |
| } |
| |
| byte readByte() { |
| ubyte[1] b = void; |
| trans_.readAll(b); |
| return cast(byte)b[0]; |
| } |
| |
| short readI16() { |
| IntBuf!short b = void; |
| trans_.readAll(b.bytes); |
| return netToHost(b.value); |
| } |
| |
| int readI32() { |
| IntBuf!int b = void; |
| trans_.readAll(b.bytes); |
| return netToHost(b.value); |
| } |
| |
| long readI64() { |
| IntBuf!long b = void; |
| trans_.readAll(b.bytes); |
| return netToHost(b.value); |
| } |
| |
| double readDouble() { |
| IntBuf!long b = void; |
| trans_.readAll(b.bytes); |
| b.value = netToHost(b.value); |
| return *cast(double*)(&b.value); |
| } |
| |
| string readString() { |
| return cast(string)readBinary(); |
| } |
| |
| ubyte[] readBinary() { |
| return readBinaryBody(readSize(stringSizeLimit)); |
| } |
| |
| TMessage readMessageBegin() { |
| TMessage msg = void; |
| |
| int size = readI32(); |
| if (size < 0) { |
| int versn = size & VERSION_MASK; |
| if (versn != VERSION_1) { |
| throw new TProtocolException("Bad protocol version.", |
| TProtocolException.Type.BAD_VERSION); |
| } |
| |
| msg.type = cast(TMessageType)(size & MESSAGE_TYPE_MASK); |
| msg.name = readString(); |
| msg.seqid = readI32(); |
| } else { |
| if (strictRead) { |
| throw new TProtocolException( |
| "Protocol version missing, old client?", |
| TProtocolException.Type.BAD_VERSION); |
| } else { |
| if (size < 0) { |
| throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); |
| } |
| msg.name = cast(string)readBinaryBody(size); |
| msg.type = cast(TMessageType)(readByte()); |
| msg.seqid = readI32(); |
| } |
| } |
| |
| return msg; |
| } |
| void readMessageEnd() {} |
| |
| TStruct readStructBegin() { |
| return TStruct(); |
| } |
| void readStructEnd() {} |
| |
| TField readFieldBegin() { |
| TField f = void; |
| f.name = null; |
| f.type = cast(TType)readByte(); |
| if (f.type == TType.STOP) return f; |
| f.id = readI16(); |
| return f; |
| } |
| void readFieldEnd() {} |
| |
| TList readListBegin() { |
| return TList(cast(TType)readByte(), readSize(containerSizeLimit)); |
| } |
| void readListEnd() {} |
| |
| TMap readMapBegin() { |
| return TMap(cast(TType)readByte(), cast(TType)readByte(), |
| readSize(containerSizeLimit)); |
| } |
| void readMapEnd() {} |
| |
| TSet readSetBegin() { |
| return TSet(cast(TType)readByte(), readSize(containerSizeLimit)); |
| } |
| void readSetEnd() {} |
| |
| private: |
| ubyte[] readBinaryBody(int size) { |
| if (size == 0) { |
| return null; |
| } |
| |
| auto buf = uninitializedArray!(ubyte[])(size); |
| trans_.readAll(buf); |
| return buf; |
| } |
| |
| int readSize(int limit) { |
| auto size = readI32(); |
| if (size < 0) { |
| throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); |
| } else if (limit > 0 && size > limit) { |
| throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); |
| } |
| return size; |
| } |
| |
| enum MESSAGE_TYPE_MASK = 0x000000ff; |
| enum VERSION_MASK = 0xffff0000; |
| enum VERSION_1 = 0x80010000; |
| |
| Transport trans_; |
| } |
| |
| /** |
| * TBinaryProtocol construction helper to avoid having to explicitly specify |
| * the transport type, i.e. to allow the constructor being called using IFTI |
| * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla |
| * enhancement requet 6082)). |
| */ |
| TBinaryProtocol!Transport tBinaryProtocol(Transport)(Transport trans, |
| int containerSizeLimit = 0, int stringSizeLimit = 0, |
| bool strictRead = false, bool strictWrite = true |
| ) if (isTTransport!Transport) { |
| return new TBinaryProtocol!Transport(trans, containerSizeLimit, |
| stringSizeLimit, strictRead, strictWrite); |
| } |
| |
| unittest { |
| import std.exception; |
| import thrift.transport.memory; |
| |
| // Check the message header format. |
| auto buf = new TMemoryBuffer; |
| auto binary = tBinaryProtocol(buf); |
| binary.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0)); |
| |
| auto header = new ubyte[15]; |
| buf.readAll(header); |
| enforce(header == [ |
| 128, 1, 0, 1, // Version 1, TMessageType.CALL |
| 0, 0, 0, 3, // Method name length |
| 102, 111, 111, // Method name ("foo") |
| 0, 0, 0, 0, // Sequence id |
| ]); |
| } |
| |
| unittest { |
| import thrift.internal.test.protocol; |
| testContainerSizeLimit!(TBinaryProtocol!())(); |
| testStringSizeLimit!(TBinaryProtocol!())(); |
| } |
| |
| /** |
| * TProtocolFactory creating a TBinaryProtocol instance for passed in |
| * transports. |
| * |
| * The optional Transports template tuple parameter can be used to specify |
| * one or more TTransport implementations to specifically instantiate |
| * TBinaryProtocol for. If the actual transport types encountered at |
| * runtime match one of the transports in the list, a specialized protocol |
| * instance is created. Otherwise, a generic TTransport version is used. |
| */ |
| class TBinaryProtocolFactory(Transports...) if ( |
| allSatisfy!(isTTransport, Transports) |
| ) : TProtocolFactory { |
| /// |
| this (int containerSizeLimit = 0, int stringSizeLimit = 0, |
| bool strictRead = false, bool strictWrite = true |
| ) { |
| strictRead_ = strictRead; |
| strictWrite_ = strictWrite; |
| containerSizeLimit_ = containerSizeLimit; |
| stringSizeLimit_ = stringSizeLimit; |
| } |
| |
| TProtocol getProtocol(TTransport trans) const { |
| foreach (Transport; TypeTuple!(Transports, TTransport)) { |
| auto concreteTrans = cast(Transport)trans; |
| if (concreteTrans) { |
| return new TBinaryProtocol!Transport(concreteTrans, |
| containerSizeLimit_, stringSizeLimit_, strictRead_, strictWrite_); |
| } |
| } |
| throw new TProtocolException( |
| "Passed null transport to TBinaryProtocolFactoy."); |
| } |
| |
| protected: |
| bool strictRead_; |
| bool strictWrite_; |
| int containerSizeLimit_; |
| int stringSizeLimit_; |
| } |