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 }