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 20 /** 21 * Defines the basic interface for a Thrift protocol and associated exception 22 * types. 23 * 24 * Most parts of the protocol API are typically not used in client code, as 25 * the actual serialization code is generated by thrift.codegen.* – the only 26 * interesting thing usually is that there are protocols which can be created 27 * from transports and passed around. 28 */ 29 module thrift.protocol.base; 30 31 import thrift.base; 32 import thrift.transport.base; 33 34 /** 35 * The field types Thrift protocols support. 36 */ 37 enum TType : byte { 38 STOP = 0, /// Used to mark the end of a sequence of fields. 39 VOID = 1, /// 40 BOOL = 2, /// 41 BYTE = 3, /// 42 DOUBLE = 4, /// 43 I16 = 6, /// 44 I32 = 8, /// 45 I64 = 10, /// 46 STRING = 11, /// 47 STRUCT = 12, /// 48 MAP = 13, /// 49 SET = 14, /// 50 LIST = 15 /// 51 } 52 53 /** 54 * Types of Thrift RPC messages. 55 */ 56 enum TMessageType : byte { 57 CALL = 1, /// Call of a normal, two-way RPC method. 58 REPLY = 2, /// Reply to a normal method call. 59 EXCEPTION = 3, /// Reply to a method call if target raised a TApplicationException. 60 ONEWAY = 4 /// Call of a one-way RPC method which is not followed by a reply. 61 } 62 63 /** 64 * Descriptions of Thrift entities. 65 */ 66 struct TField { 67 string name; 68 TType type; 69 short id; 70 } 71 72 /// ditto 73 struct TList { 74 TType elemType; 75 size_t size; 76 } 77 78 /// ditto 79 struct TMap { 80 TType keyType; 81 TType valueType; 82 size_t size; 83 } 84 85 /// ditto 86 struct TMessage { 87 string name; 88 TMessageType type; 89 int seqid; 90 } 91 92 /// ditto 93 struct TSet { 94 TType elemType; 95 size_t size; 96 } 97 98 /// ditto 99 struct TStruct { 100 string name; 101 } 102 103 /** 104 * Interface for a Thrift protocol implementation. Essentially, it defines 105 * a way of reading and writing all the base types, plus a mechanism for 106 * writing out structs with indexed fields. 107 * 108 * TProtocol objects should not be shared across multiple encoding contexts, 109 * as they may need to maintain internal state in some protocols (e.g. JSON). 110 * Note that is is acceptable for the TProtocol module to do its own internal 111 * buffered reads/writes to the underlying TTransport where appropriate (i.e. 112 * when parsing an input XML stream, reading could be batched rather than 113 * looking ahead character by character for a close tag). 114 */ 115 interface TProtocol { 116 /// The underlying transport used by the protocol. 117 TTransport transport() @property; 118 119 /* 120 * Writing methods. 121 */ 122 123 void writeBool(bool b); /// 124 void writeByte(byte b); /// 125 void writeI16(short i16); /// 126 void writeI32(int i32); /// 127 void writeI64(long i64); /// 128 void writeDouble(double dub); /// 129 void writeString(string str); /// 130 void writeBinary(ubyte[] buf); /// 131 132 void writeMessageBegin(TMessage message); /// 133 void writeMessageEnd(); /// 134 void writeStructBegin(TStruct tstruct); /// 135 void writeStructEnd(); /// 136 void writeFieldBegin(TField field); /// 137 void writeFieldEnd(); /// 138 void writeFieldStop(); /// 139 void writeListBegin(TList list); /// 140 void writeListEnd(); /// 141 void writeMapBegin(TMap map); /// 142 void writeMapEnd(); /// 143 void writeSetBegin(TSet set); /// 144 void writeSetEnd(); /// 145 146 /* 147 * Reading methods. 148 */ 149 150 bool readBool(); /// 151 byte readByte(); /// 152 short readI16(); /// 153 int readI32(); /// 154 long readI64(); /// 155 double readDouble(); /// 156 string readString(); /// 157 ubyte[] readBinary(); /// 158 159 TMessage readMessageBegin(); /// 160 void readMessageEnd(); /// 161 TStruct readStructBegin(); /// 162 void readStructEnd(); /// 163 TField readFieldBegin(); /// 164 void readFieldEnd(); /// 165 TList readListBegin(); /// 166 void readListEnd(); /// 167 TMap readMapBegin(); /// 168 void readMapEnd(); /// 169 TSet readSetBegin(); /// 170 void readSetEnd(); /// 171 172 /** 173 * Reset any internal state back to a blank slate, if the protocol is 174 * stateful. 175 */ 176 void reset(); 177 } 178 179 /** 180 * true if T is a TProtocol. 181 */ 182 template isTProtocol(T) { 183 enum isTProtocol = is(T : TProtocol); 184 } 185 186 unittest { 187 static assert(isTProtocol!TProtocol); 188 static assert(!isTProtocol!void); 189 } 190 191 /** 192 * Creates a protocol operating on a given transport. 193 */ 194 interface TProtocolFactory { 195 /// 196 TProtocol getProtocol(TTransport trans); 197 } 198 199 /** 200 * A protocol-level exception. 201 */ 202 class TProtocolException : TException { 203 /// The possible exception types. 204 enum Type { 205 UNKNOWN, /// 206 INVALID_DATA, /// 207 NEGATIVE_SIZE, /// 208 SIZE_LIMIT, /// 209 BAD_VERSION, /// 210 NOT_IMPLEMENTED, /// 211 DEPTH_LIMIT /// 212 } 213 214 /// 215 this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 216 static string msgForType(Type type) { 217 switch (type) { 218 case Type.UNKNOWN: return "Unknown protocol exception"; 219 case Type.INVALID_DATA: return "Invalid data"; 220 case Type.NEGATIVE_SIZE: return "Negative size"; 221 case Type.SIZE_LIMIT: return "Exceeded size limit"; 222 case Type.BAD_VERSION: return "Invalid version"; 223 case Type.NOT_IMPLEMENTED: return "Not implemented"; 224 case Type.DEPTH_LIMIT: return "Exceeded size limit"; 225 default: return "(Invalid exception type)"; 226 } 227 } 228 this(msgForType(type), type, file, line, next); 229 } 230 231 /// 232 this(string msg, string file = __FILE__, size_t line = __LINE__, 233 Throwable next = null) 234 { 235 this(msg, Type.UNKNOWN, file, line, next); 236 } 237 238 /// 239 this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, 240 Throwable next = null) 241 { 242 super(msg, file, line, next); 243 type_ = type; 244 } 245 246 /// 247 Type type() const @property { 248 return type_; 249 } 250 251 protected: 252 Type type_; 253 } 254 255 /** 256 * Skips a field of the given type on the protocol. 257 * 258 * The main purpose of skip() is to allow treating struct and container types, 259 * (where multiple primitive types have to be skipped) the same as scalar types 260 * in generated code. 261 */ 262 void skip(Protocol)(Protocol prot, TType type) if (is(Protocol : TProtocol)) { 263 switch (type) { 264 case TType.BOOL: 265 prot.readBool(); 266 break; 267 268 case TType.BYTE: 269 prot.readByte(); 270 break; 271 272 case TType.I16: 273 prot.readI16(); 274 break; 275 276 case TType.I32: 277 prot.readI32(); 278 break; 279 280 case TType.I64: 281 prot.readI64(); 282 break; 283 284 case TType.DOUBLE: 285 prot.readDouble(); 286 break; 287 288 case TType.STRING: 289 prot.readBinary(); 290 break; 291 292 case TType.STRUCT: 293 prot.readStructBegin(); 294 while (true) { 295 auto f = prot.readFieldBegin(); 296 if (f.type == TType.STOP) break; 297 skip(prot, f.type); 298 prot.readFieldEnd(); 299 } 300 prot.readStructEnd(); 301 break; 302 303 case TType.LIST: 304 auto l = prot.readListBegin(); 305 foreach (i; 0 .. l.size) { 306 skip(prot, l.elemType); 307 } 308 prot.readListEnd(); 309 break; 310 311 case TType.MAP: 312 auto m = prot.readMapBegin(); 313 foreach (i; 0 .. m.size) { 314 skip(prot, m.keyType); 315 skip(prot, m.valueType); 316 } 317 prot.readMapEnd(); 318 break; 319 320 case TType.SET: 321 auto s = prot.readSetBegin(); 322 foreach (i; 0 .. s.size) { 323 skip(prot, s.elemType); 324 } 325 prot.readSetEnd(); 326 break; 327 328 default: 329 throw new TProtocolException(TProtocolException.Type.INVALID_DATA); 330 } 331 } 332 333 /** 334 * Application-level exception. 335 * 336 * It is thrown if an RPC call went wrong on the application layer, e.g. if 337 * the receiver does not know the method name requested or a method invoked by 338 * the service processor throws an exception not part of the Thrift API. 339 */ 340 class TApplicationException : TException { 341 /// The possible exception types. 342 enum Type { 343 UNKNOWN = 0, /// 344 UNKNOWN_METHOD = 1, /// 345 INVALID_MESSAGE_TYPE = 2, /// 346 WRONG_METHOD_NAME = 3, /// 347 BAD_SEQUENCE_ID = 4, /// 348 MISSING_RESULT = 5, /// 349 INTERNAL_ERROR = 6, /// 350 PROTOCOL_ERROR = 7, /// 351 INVALID_TRANSFORM = 8, /// 352 INVALID_PROTOCOL = 9, /// 353 UNSUPPORTED_CLIENT_TYPE = 10 /// 354 } 355 356 /// 357 this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 358 static string msgForType(Type type) { 359 switch (type) { 360 case Type.UNKNOWN: return "Unknown application exception"; 361 case Type.UNKNOWN_METHOD: return "Unknown method"; 362 case Type.INVALID_MESSAGE_TYPE: return "Invalid message type"; 363 case Type.WRONG_METHOD_NAME: return "Wrong method name"; 364 case Type.BAD_SEQUENCE_ID: return "Bad sequence identifier"; 365 case Type.MISSING_RESULT: return "Missing result"; 366 case Type.INTERNAL_ERROR: return "Internal error"; 367 case Type.PROTOCOL_ERROR: return "Protocol error"; 368 case Type.INVALID_TRANSFORM: return "Invalid transform"; 369 case Type.INVALID_PROTOCOL: return "Invalid protocol"; 370 case Type.UNSUPPORTED_CLIENT_TYPE: return "Unsupported client type"; 371 default: return "(Invalid exception type)"; 372 } 373 } 374 this(msgForType(type), type, file, line, next); 375 } 376 377 /// 378 this(string msg, string file = __FILE__, size_t line = __LINE__, 379 Throwable next = null) 380 { 381 this(msg, Type.UNKNOWN, file, line, next); 382 } 383 384 /// 385 this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, 386 Throwable next = null) 387 { 388 super(msg, file, line, next); 389 type_ = type; 390 } 391 392 /// 393 Type type() @property const { 394 return type_; 395 } 396 397 // TODO: Replace hand-written read()/write() with thrift.codegen templates. 398 399 /// 400 void read(TProtocol iprot) { 401 iprot.readStructBegin(); 402 while (true) { 403 auto f = iprot.readFieldBegin(); 404 if (f.type == TType.STOP) break; 405 406 switch (f.id) { 407 case 1: 408 if (f.type == TType.STRING) { 409 msg = iprot.readString(); 410 } else { 411 skip(iprot, f.type); 412 } 413 break; 414 case 2: 415 if (f.type == TType.I32) { 416 type_ = cast(Type)iprot.readI32(); 417 } else { 418 skip(iprot, f.type); 419 } 420 break; 421 default: 422 skip(iprot, f.type); 423 break; 424 } 425 } 426 iprot.readStructEnd(); 427 } 428 429 /// 430 void write(TProtocol oprot) const { 431 oprot.writeStructBegin(TStruct("TApplicationException")); 432 433 if (msg != null) { 434 oprot.writeFieldBegin(TField("message", TType.STRING, 1)); 435 oprot.writeString(msg); 436 oprot.writeFieldEnd(); 437 } 438 439 oprot.writeFieldBegin(TField("type", TType.I32, 2)); 440 oprot.writeI32(type_); 441 oprot.writeFieldEnd(); 442 443 oprot.writeFieldStop(); 444 oprot.writeStructEnd(); 445 } 446 447 private: 448 Type type_; 449 }