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.compact; 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 * D implementation of the Compact protocol. 29 * 30 * See THRIFT-110 for a protocol description. This implementation is based on 31 * the C++ one. 32 */ 33 final class TCompactProtocol(Transport = TTransport) if ( 34 isTTransport!Transport 35 ) : TProtocol { 36 /** 37 * Constructs a new instance. 38 * 39 * Params: 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. 45 */ 46 this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) { 47 trans_ = trans; 48 this.containerSizeLimit = containerSizeLimit; 49 this.stringSizeLimit = stringSizeLimit; 50 } 51 transport()52 Transport transport() @property { 53 return trans_; 54 } 55 reset()56 void reset() { 57 lastFieldId_ = 0; 58 fieldIdStack_ = null; 59 booleanField_ = TField.init; 60 hasBoolValue_ = false; 61 } 62 63 /** 64 * If positive, limits the number of items of deserialized containers to the 65 * given amount. 66 * 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. 70 * 71 * Defaults to zero (no limit). 72 */ 73 int containerSizeLimit; 74 75 /** 76 * If positive, limits the length of deserialized strings/binary data to the 77 * given number of bytes. 78 * 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. 82 * 83 * Defaults to zero (no limit). 84 */ 85 int stringSizeLimit; 86 87 /* 88 * Writing methods. 89 */ 90 writeBool(bool b)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; 97 } else { 98 // we're not part of a field, so just write the value 99 writeByte(b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE); 100 } 101 } 102 writeByte(byte b)103 void writeByte(byte b) { 104 trans_.write((cast(ubyte*)&b)[0..1]); 105 } 106 writeI16(short i16)107 void writeI16(short i16) { 108 writeVarint32(i32ToZigzag(i16)); 109 } 110 writeI32(int i32)111 void writeI32(int i32) { 112 writeVarint32(i32ToZigzag(i32)); 113 } 114 writeI64(long i64)115 void writeI64(long i64) { 116 writeVarint64(i64ToZigzag(i64)); 117 } 118 writeDouble(double dub)119 void writeDouble(double dub) { 120 ulong bits = hostToLe(*cast(ulong*)(&dub)); 121 trans_.write((cast(ubyte*)&bits)[0 .. 8]); 122 } 123 writeString(string str)124 void writeString(string str) { 125 writeBinary(cast(ubyte[])str); 126 } 127 writeBinary(ubyte[]buf)128 void writeBinary(ubyte[] buf) { 129 assert(buf.length <= int.max); 130 writeVarint32(cast(int)buf.length); 131 trans_.write(buf); 132 } 133 writeMessageBegin(TMessage msg)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); 140 } writeMessageEnd()141 void writeMessageEnd() {} 142 writeStructBegin(TStruct tstruct)143 void writeStructBegin(TStruct tstruct) { 144 fieldIdStack_ ~= lastFieldId_; 145 lastFieldId_ = 0; 146 } 147 writeStructEnd()148 void writeStructEnd() { 149 lastFieldId_ = fieldIdStack_[$ - 1]; 150 fieldIdStack_ = fieldIdStack_[0 .. $ - 1]; 151 fieldIdStack_.assumeSafeAppend(); 152 } 153 writeFieldBegin(TField field)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; 159 } else { 160 return writeFieldBeginInternal(field); 161 } 162 } writeFieldEnd()163 void writeFieldEnd() {} 164 writeFieldStop()165 void writeFieldStop() { 166 writeByte(TType.STOP); 167 } 168 writeListBegin(TList list)169 void writeListBegin(TList list) { 170 writeCollectionBegin(list.elemType, list.size); 171 } writeListEnd()172 void writeListEnd() {} 173 writeMapBegin(TMap map)174 void writeMapBegin(TMap map) { 175 if (map.size == 0) { 176 writeByte(0); 177 } else { 178 assert(map.size <= int.max); 179 writeVarint32(cast(int)map.size); 180 writeByte(cast(byte)(toCType(map.keyType) << 4 | toCType(map.valueType))); 181 } 182 } writeMapEnd()183 void writeMapEnd() {} 184 writeSetBegin(TSet set)185 void writeSetBegin(TSet set) { 186 writeCollectionBegin(set.elemType, set.size); 187 } writeSetEnd()188 void writeSetEnd() {} 189 190 191 /* 192 * Reading methods. 193 */ 194 readBool()195 bool readBool() { 196 if (hasBoolValue_ == true) { 197 hasBoolValue_ = false; 198 return boolValue_; 199 } 200 201 return readByte() == CType.BOOLEAN_TRUE; 202 } 203 readByte()204 byte readByte() { 205 ubyte[1] b = void; 206 trans_.readAll(b); 207 return cast(byte)b[0]; 208 } 209 readI16()210 short readI16() { 211 return cast(short)zigzagToI32(readVarint32()); 212 } 213 readI32()214 int readI32() { 215 return zigzagToI32(readVarint32()); 216 } 217 readI64()218 long readI64() { 219 return zigzagToI64(readVarint64()); 220 } 221 readDouble()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); 227 } 228 readString()229 string readString() { 230 return cast(string)readBinary(); 231 } 232 readBinary()233 ubyte[] readBinary() { 234 auto size = readVarint32(); 235 checkSize(size, stringSizeLimit); 236 237 if (size == 0) { 238 return null; 239 } 240 241 auto buf = uninitializedArray!(ubyte[])(size); 242 trans_.readAll(buf); 243 return buf; 244 } 245 readMessageBegin()246 TMessage readMessageBegin() { 247 TMessage msg = void; 248 249 auto protocolId = readByte(); 250 if (protocolId != cast(byte)PROTOCOL_ID) { 251 throw new TProtocolException("Bad protocol identifier", 252 TProtocolException.Type.BAD_VERSION); 253 } 254 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); 260 } 261 262 msg.type = cast(TMessageType)((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS); 263 msg.seqid = readVarint32(); 264 msg.name = readString(); 265 266 return msg; 267 } readMessageEnd()268 void readMessageEnd() {} 269 readStructBegin()270 TStruct readStructBegin() { 271 fieldIdStack_ ~= lastFieldId_; 272 lastFieldId_ = 0; 273 return TStruct(); 274 } 275 readStructEnd()276 void readStructEnd() { 277 lastFieldId_ = fieldIdStack_[$ - 1]; 278 fieldIdStack_ = fieldIdStack_[0 .. $ - 1]; 279 } 280 readFieldBegin()281 TField readFieldBegin() { 282 TField f = void; 283 f.name = null; 284 285 auto bite = readByte(); 286 auto type = cast(CType)(bite & 0x0f); 287 288 if (type == CType.STOP) { 289 // Struct stop byte, nothing more to do. 290 f.id = 0; 291 f.type = TType.STOP; 292 return f; 293 } 294 295 // Mask off the 4 MSB of the type header, which could contain a field id 296 // delta. 297 auto modifier = cast(short)((bite & 0xf0) >> 4); 298 if (modifier > 0) { 299 f.id = cast(short)(lastFieldId_ + modifier); 300 } else { 301 // Delta encoding not used, just read the id as usual. 302 f.id = readI16(); 303 } 304 f.type = getTType(type); 305 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); 311 } 312 313 lastFieldId_ = f.id; 314 return f; 315 } readFieldEnd()316 void readFieldEnd() {} 317 readListBegin()318 TList readListBegin() { 319 auto sizeAndType = readByte(); 320 321 auto lsize = (sizeAndType >> 4) & 0xf; 322 if (lsize == 0xf) { 323 lsize = readVarint32(); 324 } 325 checkSize(lsize, containerSizeLimit); 326 327 TList l = void; 328 l.elemType = getTType(cast(CType)(sizeAndType & 0x0f)); 329 l.size = cast(size_t)lsize; 330 331 return l; 332 } readListEnd()333 void readListEnd() {} 334 readMapBegin()335 TMap readMapBegin() { 336 TMap m = void; 337 338 auto size = readVarint32(); 339 ubyte kvType; 340 if (size != 0) { 341 kvType = readByte(); 342 } 343 checkSize(size, containerSizeLimit); 344 345 m.size = size; 346 m.keyType = getTType(cast(CType)(kvType >> 4)); 347 m.valueType = getTType(cast(CType)(kvType & 0xf)); 348 349 return m; 350 } readMapEnd()351 void readMapEnd() {} 352 readSetBegin()353 TSet readSetBegin() { 354 auto sizeAndType = readByte(); 355 356 auto lsize = (sizeAndType >> 4) & 0xf; 357 if (lsize == 0xf) { 358 lsize = readVarint32(); 359 } 360 checkSize(lsize, containerSizeLimit); 361 362 TSet s = void; 363 s.elemType = getTType(cast(CType)(sizeAndType & 0xf)); 364 s.size = cast(size_t)lsize; 365 366 return s; 367 } readSetEnd()368 void readSetEnd() {} 369 370 private: 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); 374 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)); 379 } else { 380 // write them separate 381 writeByte(cast(byte)typeToWrite); 382 writeI16(field.id); 383 } 384 385 lastFieldId_ = field.id; 386 } 387 388 writeCollectionBegin(TType elemType,size_t size)389 void writeCollectionBegin(TType elemType, size_t size) { 390 if (size <= 14) { 391 writeByte(cast(byte)(size << 4 | toCType(elemType))); 392 } else { 393 assert(size <= int.max); 394 writeByte(cast(byte)(0xf0 | toCType(elemType))); 395 writeVarint32(cast(int)size); 396 } 397 } 398 writeVarint32(uint n)399 void writeVarint32(uint n) { 400 ubyte[5] buf = void; 401 ubyte wsize; 402 403 while (true) { 404 if ((n & ~0x7F) == 0) { 405 buf[wsize++] = cast(ubyte)n; 406 break; 407 } else { 408 buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80); 409 n >>= 7; 410 } 411 } 412 413 trans_.write(buf[0 .. wsize]); 414 } 415 416 /* 417 * Write an i64 as a varint. Results in 1-10 bytes on the wire. 418 */ writeVarint64(ulong n)419 void writeVarint64(ulong n) { 420 ubyte[10] buf = void; 421 ubyte wsize; 422 423 while (true) { 424 if ((n & ~0x7FL) == 0) { 425 buf[wsize++] = cast(ubyte)n; 426 break; 427 } else { 428 buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80); 429 n >>= 7; 430 } 431 } 432 433 trans_.write(buf[0 .. wsize]); 434 } 435 436 /* 437 * Convert l into a zigzag long. This allows negative numbers to be 438 * represented compactly as a varint. 439 */ i64ToZigzag(long l)440 ulong i64ToZigzag(long l) { 441 return (l << 1) ^ (l >> 63); 442 } 443 444 /* 445 * Convert n into a zigzag int. This allows negative numbers to be 446 * represented compactly as a varint. 447 */ i32ToZigzag(int n)448 uint i32ToZigzag(int n) { 449 return (n << 1) ^ (n >> 31); 450 } 451 toCType(TType type)452 CType toCType(TType type) { 453 final switch (type) { 454 case TType.STOP: 455 return CType.STOP; 456 case TType.BOOL: 457 return CType.BOOLEAN_TRUE; 458 case TType.BYTE: 459 return CType.BYTE; 460 case TType.DOUBLE: 461 return CType.DOUBLE; 462 case TType.I16: 463 return CType.I16; 464 case TType.I32: 465 return CType.I32; 466 case TType.I64: 467 return CType.I64; 468 case TType.STRING: 469 return CType.BINARY; 470 case TType.STRUCT: 471 return CType.STRUCT; 472 case TType.MAP: 473 return CType.MAP; 474 case TType.SET: 475 return CType.SET; 476 case TType.LIST: 477 return CType.LIST; 478 case TType.VOID: 479 assert(false, "Invalid type passed."); 480 } 481 } 482 readVarint32()483 int readVarint32() { 484 return cast(int)readVarint64(); 485 } 486 readVarint64()487 long readVarint64() { 488 ulong val; 489 ubyte shift; 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); 493 494 ubyte rsize; 495 496 if (borrowed) { 497 // Fast path. 498 while (true) { 499 auto bite = borrowed[rsize]; 500 rsize++; 501 val |= cast(ulong)(bite & 0x7f) << shift; 502 shift += 7; 503 if (!(bite & 0x80)) { 504 trans_.consume(rsize); 505 return val; 506 } 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."); 511 } 512 } 513 } else { 514 // Slow path. 515 while (true) { 516 ubyte[1] bite; 517 trans_.readAll(bite); 518 ++rsize; 519 520 val |= cast(ulong)(bite[0] & 0x7f) << shift; 521 shift += 7; 522 if (!(bite[0] & 0x80)) { 523 return val; 524 } 525 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."); 530 } 531 } 532 } 533 } 534 535 /* 536 * Convert from zigzag int to int. 537 */ zigzagToI32(uint n)538 int zigzagToI32(uint n) { 539 return (n >> 1) ^ -(n & 1); 540 } 541 542 /* 543 * Convert from zigzag long to long. 544 */ zigzagToI64(ulong n)545 long zigzagToI64(ulong n) { 546 return (n >> 1) ^ -(n & 1); 547 } 548 getTType(CType type)549 TType getTType(CType type) { 550 final switch (type) { 551 case CType.STOP: 552 return TType.STOP; 553 case CType.BOOLEAN_FALSE: 554 return TType.BOOL; 555 case CType.BOOLEAN_TRUE: 556 return TType.BOOL; 557 case CType.BYTE: 558 return TType.BYTE; 559 case CType.I16: 560 return TType.I16; 561 case CType.I32: 562 return TType.I32; 563 case CType.I64: 564 return TType.I64; 565 case CType.DOUBLE: 566 return TType.DOUBLE; 567 case CType.BINARY: 568 return TType.STRING; 569 case CType.LIST: 570 return TType.LIST; 571 case CType.SET: 572 return TType.SET; 573 case CType.MAP: 574 return TType.MAP; 575 case CType.STRUCT: 576 return TType.STRUCT; 577 } 578 } 579 checkSize(int size,int limit)580 void checkSize(int size, int limit) { 581 if (size < 0) { 582 throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); 583 } else if (limit > 0 && size > limit) { 584 throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); 585 } 586 } 587 588 enum PROTOCOL_ID = 0x82; 589 enum VERSION_N = 1; 590 enum VERSION_MASK = 0b0001_1111; 591 enum TYPE_MASK = 0b1110_0000; 592 enum TYPE_BITS = 0b0000_0111; 593 enum TYPE_SHIFT_AMOUNT = 5; 594 595 // Probably need to implement a better stack at some point. 596 short[] fieldIdStack_; 597 short lastFieldId_; 598 599 TField booleanField_; 600 601 bool hasBoolValue_; 602 bool boolValue_; 603 604 Transport trans_; 605 } 606 607 /** 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)). 612 */ 613 TCompactProtocol!Transport tCompactProtocol(Transport)(Transport trans, 614 int containerSizeLimit = 0, int stringSizeLimit = 0 615 ) if (isTTransport!Transport) 616 { 617 return new TCompactProtocol!Transport(trans, 618 containerSizeLimit, stringSizeLimit); 619 } 620 621 private { 622 enum CType : ubyte { 623 STOP = 0x0, 624 BOOLEAN_TRUE = 0x1, 625 BOOLEAN_FALSE = 0x2, 626 BYTE = 0x3, 627 I16 = 0x4, 628 I32 = 0x5, 629 I64 = 0x6, 630 DOUBLE = 0x7, 631 BINARY = 0x8, 632 LIST = 0x9, 633 SET = 0xa, 634 MAP = 0xb, 635 STRUCT = 0xc 636 } 637 static assert(CType.max <= 0xf, 638 "Compact protocol wire type representation must fit into 4 bits."); 639 } 640 641 unittest { 642 import std.exception; 643 import thrift.transport.memory; 644 645 // Check the message header format. 646 auto buf = new TMemoryBuffer; 647 auto compact = tCompactProtocol(buf); 648 compact.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0)); 649 650 auto header = new ubyte[7]; 651 buf.readAll(header); 652 enforce(header == [ 653 130, // Protocol id. 654 33, // Version/type byte. 655 0, // Sequence id. 656 3, 102, 111, 111 // Method name. 657 ]); 658 } 659 660 unittest { 661 import thrift.internal.test.protocol; 662 testContainerSizeLimit!(TCompactProtocol!())(); 663 testStringSizeLimit!(TCompactProtocol!())(); 664 } 665 666 /** 667 * TProtocolFactory creating a TCompactProtocol instance for passed in 668 * transports. 669 * 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. 675 */ 676 class TCompactProtocolFactory(Transports...) if ( 677 allSatisfy!(isTTransport, Transports) 678 ) : TProtocolFactory { 679 /// 680 this(int containerSizeLimit = 0, int stringSizeLimit = 0) { 681 containerSizeLimit_ = 0; 682 stringSizeLimit_ = 0; 683 } 684 getProtocol(TTransport trans)685 TProtocol getProtocol(TTransport trans) const { 686 foreach (Transport; TypeTuple!(Transports, TTransport)) { 687 auto concreteTrans = cast(Transport)trans; 688 if (concreteTrans) { 689 return new TCompactProtocol!Transport(concreteTrans); 690 } 691 } 692 throw new TProtocolException( 693 "Passed null transport to TCompactProtocolFactory."); 694 } 695 696 int containerSizeLimit_; 697 int stringSizeLimit_; 698 } 699