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.json;
20 
21 import std.algorithm;
22 import std.array;
23 import std.base64;
24 import std.conv;
25 import std.range;
26 import std..string : format;
27 import std.traits : isIntegral;
28 import std.typetuple : allSatisfy, TypeTuple;
29 import thrift.protocol.base;
30 import thrift.transport.base;
31 
32 alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad;
33 
34 /**
35  * Implementation of the Thrift JSON protocol.
36  */
37 final class TJsonProtocol(Transport = TTransport) if (
38   isTTransport!Transport
39 ) : TProtocol {
40   /**
41    * Constructs a new instance.
42    *
43    * Params:
44    *   trans = The transport to use.
45    *   containerSizeLimit = If positive, the container size is limited to the
46    *     given number of items.
47    *   stringSizeLimit = If positive, the string length is limited to the
48    *     given number of bytes.
49    */
50   this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
51     trans_ = trans;
52     this.containerSizeLimit = containerSizeLimit;
53     this.stringSizeLimit = stringSizeLimit;
54 
55     context_ = new Context();
56     reader_ = new LookaheadReader(trans);
57   }
58 
59   Transport transport() @property {
60     return trans_;
61   }
62 
63   void reset() {
64     contextStack_.clear();
65     context_ = new Context();
66     reader_ = new LookaheadReader(trans_);
67   }
68 
69   /**
70    * If positive, limits the number of items of deserialized containers to the
71    * given amount.
72    *
73    * This is useful to avoid allocating excessive amounts of memory when broken
74    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
75    * TProtocolException is thrown.
76    *
77    * Defaults to zero (no limit).
78    */
79   int containerSizeLimit;
80 
81   /**
82    * If positive, limits the length of deserialized strings/binary data to the
83    * given number of bytes.
84    *
85    * This is useful to avoid allocating excessive amounts of memory when broken
86    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
87    * TProtocolException is thrown.
88    *
89    * Note: For binary data, the limit applies to the length of the
90    * Base64-encoded string data, not the resulting byte array.
91    *
92    * Defaults to zero (no limit).
93    */
94   int stringSizeLimit;
95 
96   /*
97    * Writing methods.
98    */
99 
100   void writeBool(bool b) {
101     writeJsonInteger(b ? 1 : 0);
102   }
103 
104   void writeByte(byte b) {
105     writeJsonInteger(b);
106   }
107 
108   void writeI16(short i16) {
109     writeJsonInteger(i16);
110   }
111 
112   void writeI32(int i32) {
113     writeJsonInteger(i32);
114   }
115 
116   void writeI64(long i64) {
117     writeJsonInteger(i64);
118   }
119 
120   void writeDouble(double dub) {
121     context_.write(trans_);
122 
123     string value;
124     if (dub is double.nan) {
125       value = NAN_STRING;
126     } else if (dub is double.infinity) {
127       value = INFINITY_STRING;
128     } else if (dub is -double.infinity) {
129       value = NEG_INFINITY_STRING;
130     }
131 
132     bool escapeNum = value !is null || context_.escapeNum;
133 
134     if (value is null) {
135       value = format("%.16g", dub);
136     }
137 
138     if (escapeNum) trans_.write(STRING_DELIMITER);
139     trans_.write(cast(ubyte[])value);
140     if (escapeNum) trans_.write(STRING_DELIMITER);
141   }
142 
143   void writeString(string str) {
144     context_.write(trans_);
145     trans_.write(STRING_DELIMITER);
146     foreach (c; str) {
147       writeJsonChar(c);
148     }
149     trans_.write(STRING_DELIMITER);
150   }
151 
152   void writeBinary(ubyte[] buf) {
153     context_.write(trans_);
154 
155     trans_.write(STRING_DELIMITER);
156     ubyte[4] b;
157     while (!buf.empty) {
158       auto toWrite = take(buf, 3);
159       Base64NoPad.encode(toWrite, b[]);
160       trans_.write(b[0 .. toWrite.length + 1]);
161       buf.popFrontN(toWrite.length);
162     }
163     trans_.write(STRING_DELIMITER);
164   }
165 
166   void writeMessageBegin(TMessage msg) {
167     writeJsonArrayBegin();
168     writeJsonInteger(THRIFT_JSON_VERSION);
169     writeString(msg.name);
170     writeJsonInteger(cast(byte)msg.type);
171     writeJsonInteger(msg.seqid);
172   }
173 
174   void writeMessageEnd() {
175     writeJsonArrayEnd();
176   }
177 
178   void writeStructBegin(TStruct tstruct) {
179     writeJsonObjectBegin();
180   }
181 
182   void writeStructEnd() {
183     writeJsonObjectEnd();
184   }
185 
186   void writeFieldBegin(TField field) {
187     writeJsonInteger(field.id);
188     writeJsonObjectBegin();
189     writeString(getNameFromTType(field.type));
190   }
191 
192   void writeFieldEnd() {
193     writeJsonObjectEnd();
194   }
195 
196   void writeFieldStop() {}
197 
198   void writeListBegin(TList list) {
199     writeJsonArrayBegin();
200     writeString(getNameFromTType(list.elemType));
201     writeJsonInteger(list.size);
202   }
203 
204   void writeListEnd() {
205     writeJsonArrayEnd();
206   }
207 
208   void writeMapBegin(TMap map) {
209     writeJsonArrayBegin();
210     writeString(getNameFromTType(map.keyType));
211     writeString(getNameFromTType(map.valueType));
212     writeJsonInteger(map.size);
213     writeJsonObjectBegin();
214   }
215 
216   void writeMapEnd() {
217     writeJsonObjectEnd();
218     writeJsonArrayEnd();
219   }
220 
221   void writeSetBegin(TSet set) {
222     writeJsonArrayBegin();
223     writeString(getNameFromTType(set.elemType));
224     writeJsonInteger(set.size);
225   }
226 
227   void writeSetEnd() {
228     writeJsonArrayEnd();
229   }
230 
231 
232   /*
233    * Reading methods.
234    */
235 
236   bool readBool() {
237     return readJsonInteger!byte() ? true : false;
238   }
239 
240   byte readByte() {
241     return readJsonInteger!byte();
242   }
243 
244   short readI16() {
245     return readJsonInteger!short();
246   }
247 
248   int readI32() {
249     return readJsonInteger!int();
250   }
251 
252   long readI64() {
253     return readJsonInteger!long();
254   }
255 
256   double readDouble() {
257     context_.read(reader_);
258 
259     if (reader_.peek() == STRING_DELIMITER) {
260       auto str = readJsonString(true);
261       if (str == NAN_STRING) {
262         return double.nan;
263       }
264       if (str == INFINITY_STRING) {
265         return double.infinity;
266       }
267       if (str == NEG_INFINITY_STRING) {
268         return -double.infinity;
269       }
270 
271       if (!context_.escapeNum) {
272         // Throw exception -- we should not be in a string in this case
273         throw new TProtocolException("Numeric data unexpectedly quoted",
274           TProtocolException.Type.INVALID_DATA);
275       }
276       try {
277         return to!double(str);
278       } catch (ConvException e) {
279         throw new TProtocolException(`Expected numeric value; got "` ~ str ~
280           `".`, TProtocolException.Type.INVALID_DATA);
281       }
282     }
283     else {
284       if (context_.escapeNum) {
285         // This will throw - we should have had a quote if escapeNum == true
286         readJsonSyntaxChar(STRING_DELIMITER);
287       }
288 
289       auto str = readJsonNumericChars();
290       try {
291         return to!double(str);
292       } catch (ConvException e) {
293         throw new TProtocolException(`Expected numeric value; got "` ~ str ~
294           `".`, TProtocolException.Type.INVALID_DATA);
295       }
296     }
297   }
298 
299   string readString() {
300     return readJsonString(false);
301   }
302 
303   ubyte[] readBinary() {
304     return Base64NoPad.decode(readString());
305   }
306 
307   TMessage readMessageBegin() {
308     TMessage msg = void;
309 
310     readJsonArrayBegin();
311 
312     auto ver = readJsonInteger!short();
313     if (ver != THRIFT_JSON_VERSION) {
314       throw new TProtocolException("Message contained bad version.",
315         TProtocolException.Type.BAD_VERSION);
316     }
317 
318     msg.name = readString();
319     msg.type = cast(TMessageType)readJsonInteger!byte();
320     msg.seqid = readJsonInteger!short();
321 
322     return msg;
323   }
324 
325   void readMessageEnd() {
326     readJsonArrayEnd();
327   }
328 
329   TStruct readStructBegin() {
330     readJsonObjectBegin();
331     return TStruct();
332   }
333 
334   void readStructEnd() {
335     readJsonObjectEnd();
336   }
337 
338   TField readFieldBegin() {
339     TField f = void;
340     f.name = null;
341 
342     auto ch = reader_.peek();
343     if (ch == OBJECT_END) {
344       f.type = TType.STOP;
345     } else {
346       f.id = readJsonInteger!short();
347       readJsonObjectBegin();
348       f.type = getTTypeFromName(readString());
349     }
350 
351     return f;
352   }
353 
354   void readFieldEnd() {
355     readJsonObjectEnd();
356   }
357 
358   TList readListBegin() {
359     readJsonArrayBegin();
360     auto type = getTTypeFromName(readString());
361     auto size = readContainerSize();
362     return TList(type, size);
363   }
364 
365   void readListEnd() {
366     readJsonArrayEnd();
367   }
368 
369   TMap readMapBegin() {
370     readJsonArrayBegin();
371     auto keyType = getTTypeFromName(readString());
372     auto valueType = getTTypeFromName(readString());
373     auto size = readContainerSize();
374     readJsonObjectBegin();
375     return TMap(keyType, valueType, size);
376   }
377 
378   void readMapEnd() {
379     readJsonObjectEnd();
380     readJsonArrayEnd();
381   }
382 
383   TSet readSetBegin() {
384     readJsonArrayBegin();
385     auto type = getTTypeFromName(readString());
386     auto size = readContainerSize();
387     return TSet(type, size);
388   }
389 
390   void readSetEnd() {
391     readJsonArrayEnd();
392   }
393 
394 private:
395   void pushContext(Context c) {
396     contextStack_ ~= context_;
397     context_ = c;
398   }
399 
400   void popContext() {
401     context_ = contextStack_.back;
402     contextStack_.popBack();
403     contextStack_.assumeSafeAppend();
404   }
405 
406   /*
407    * Writing functions
408    */
409 
410   // Write the character ch as a Json escape sequence ("\u00xx")
411   void writeJsonEscapeChar(ubyte ch) {
412     trans_.write(ESCAPE_PREFIX);
413     trans_.write(ESCAPE_PREFIX);
414     auto outCh = hexChar(cast(ubyte)(ch >> 4));
415     trans_.write((&outCh)[0 .. 1]);
416     outCh = hexChar(ch);
417     trans_.write((&outCh)[0 .. 1]);
418   }
419 
420   // Write the character ch as part of a Json string, escaping as appropriate.
421   void writeJsonChar(ubyte ch) {
422     if (ch >= 0x30) {
423       if (ch == '\\') { // Only special character >= 0x30 is '\'
424         trans_.write(BACKSLASH);
425         trans_.write(BACKSLASH);
426       } else {
427         trans_.write((&ch)[0 .. 1]);
428       }
429     }
430     else {
431       auto outCh = kJsonCharTable[ch];
432       // Check if regular character, backslash escaped, or Json escaped
433       if (outCh == 1) {
434         trans_.write((&ch)[0 .. 1]);
435       } else if (outCh > 1) {
436         trans_.write(BACKSLASH);
437         trans_.write((&outCh)[0 .. 1]);
438       } else {
439         writeJsonEscapeChar(ch);
440       }
441     }
442   }
443 
444   // Convert the given integer type to a Json number, or a string
445   // if the context requires it (eg: key in a map pair).
446   void writeJsonInteger(T)(T num) if (isIntegral!T) {
447     context_.write(trans_);
448 
449     auto escapeNum = context_.escapeNum();
450     if (escapeNum) trans_.write(STRING_DELIMITER);
451     trans_.write(cast(ubyte[])to!string(num));
452     if (escapeNum) trans_.write(STRING_DELIMITER);
453   }
454 
455   void writeJsonObjectBegin() {
456     context_.write(trans_);
457     trans_.write(OBJECT_BEGIN);
458     pushContext(new PairContext());
459   }
460 
461   void writeJsonObjectEnd() {
462     popContext();
463     trans_.write(OBJECT_END);
464   }
465 
466   void writeJsonArrayBegin() {
467     context_.write(trans_);
468     trans_.write(ARRAY_BEGIN);
469     pushContext(new ListContext());
470   }
471 
472   void writeJsonArrayEnd() {
473     popContext();
474     trans_.write(ARRAY_END);
475   }
476 
477   /*
478    * Reading functions
479    */
480 
481   int readContainerSize() {
482     auto size = readJsonInteger!int();
483     if (size < 0) {
484       throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
485     } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
486       throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
487     }
488     return size;
489   }
490 
491   void readJsonSyntaxChar(ubyte[1] ch) {
492     return readSyntaxChar(reader_, ch);
493   }
494 
495   ubyte readJsonEscapeChar() {
496     readJsonSyntaxChar(ZERO_CHAR);
497     readJsonSyntaxChar(ZERO_CHAR);
498     auto a = reader_.read();
499     auto b = reader_.read();
500     return cast(ubyte)((hexVal(a[0]) << 4) + hexVal(b[0]));
501   }
502 
503   string readJsonString(bool skipContext = false) {
504     if (!skipContext) context_.read(reader_);
505 
506     readJsonSyntaxChar(STRING_DELIMITER);
507     auto buffer = appender!string();
508 
509     int bytesRead;
510     while (true) {
511       auto ch = reader_.read();
512       if (ch == STRING_DELIMITER) {
513         break;
514       }
515 
516       ++bytesRead;
517       if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
518         throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
519       }
520 
521       if (ch == BACKSLASH) {
522         ch = reader_.read();
523         if (ch == ESCAPE_CHAR) {
524           ch = readJsonEscapeChar();
525         } else {
526           auto pos = countUntil(kEscapeChars[], ch[0]);
527           if (pos == -1) {
528             throw new TProtocolException("Expected control char, got '" ~
529               cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
530           }
531           ch = kEscapeCharVals[pos];
532         }
533       }
534       buffer.put(ch[0]);
535     }
536 
537     return buffer.data;
538   }
539 
540   // Reads a sequence of characters, stopping at the first one that is not
541   // a valid Json numeric character.
542   string readJsonNumericChars() {
543     string str;
544     while (true) {
545       auto ch = reader_.peek();
546       if (!isJsonNumeric(ch[0])) {
547         break;
548       }
549       reader_.read();
550       str ~= ch;
551     }
552     return str;
553   }
554 
555   // Reads a sequence of characters and assembles them into a number,
556   // returning them via num
557   T readJsonInteger(T)() if (isIntegral!T) {
558     context_.read(reader_);
559     if (context_.escapeNum()) {
560       readJsonSyntaxChar(STRING_DELIMITER);
561     }
562     auto str = readJsonNumericChars();
563     T num;
564     try {
565       num = to!T(str);
566     } catch (ConvException e) {
567       throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
568         TProtocolException.Type.INVALID_DATA);
569     }
570     if (context_.escapeNum()) {
571       readJsonSyntaxChar(STRING_DELIMITER);
572     }
573     return num;
574   }
575 
576   void readJsonObjectBegin() {
577     context_.read(reader_);
578     readJsonSyntaxChar(OBJECT_BEGIN);
579     pushContext(new PairContext());
580   }
581 
582   void readJsonObjectEnd() {
583     readJsonSyntaxChar(OBJECT_END);
584     popContext();
585   }
586 
587   void readJsonArrayBegin() {
588     context_.read(reader_);
589     readJsonSyntaxChar(ARRAY_BEGIN);
590     pushContext(new ListContext());
591   }
592 
593   void readJsonArrayEnd() {
594     readJsonSyntaxChar(ARRAY_END);
595     popContext();
596   }
597 
598   static {
599     final class LookaheadReader {
600       this(Transport trans) {
601         trans_ = trans;
602       }
603 
604       ubyte[1] read() {
605         if (hasData_) {
606           hasData_ = false;
607         } else {
608           trans_.readAll(data_);
609         }
610         return data_;
611       }
612 
613       ubyte[1] peek() {
614         if (!hasData_) {
615           trans_.readAll(data_);
616           hasData_ = true;
617         }
618         return data_;
619       }
620 
621      private:
622       Transport trans_;
623       bool hasData_;
624       ubyte[1] data_;
625     }
626 
627     /*
628      * Class to serve as base Json context and as base class for other context
629      * implementations
630      */
631     class Context {
632       /**
633        * Write context data to the transport. Default is to do nothing.
634        */
635       void write(Transport trans) {}
636 
637       /**
638        * Read context data from the transport. Default is to do nothing.
639        */
640       void read(LookaheadReader reader) {}
641 
642       /**
643        * Return true if numbers need to be escaped as strings in this context.
644        * Default behavior is to return false.
645        */
646       bool escapeNum() @property {
647         return false;
648       }
649     }
650 
651     // Context class for object member key-value pairs
652     class PairContext : Context {
653       this() {
654         first_ = true;
655         colon_ = true;
656       }
657 
658       override void write(Transport trans) {
659         if (first_) {
660           first_ = false;
661           colon_ = true;
662         } else {
663           trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
664           colon_ = !colon_;
665         }
666       }
667 
668       override void read(LookaheadReader reader) {
669         if (first_) {
670           first_ = false;
671           colon_ = true;
672         } else {
673           auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
674           colon_ = !colon_;
675           return readSyntaxChar(reader, ch);
676         }
677       }
678 
679       // Numbers must be turned into strings if they are the key part of a pair
680       override bool escapeNum() @property {
681         return colon_;
682       }
683 
684     private:
685       bool first_;
686       bool colon_;
687     }
688 
689     class ListContext : Context {
690       this() {
691         first_ = true;
692       }
693 
694       override void write(Transport trans) {
695         if (first_) {
696           first_ = false;
697         } else {
698           trans.write(ELEM_SEP);
699         }
700       }
701 
702       override void read(LookaheadReader reader) {
703         if (first_) {
704           first_ = false;
705         } else {
706           readSyntaxChar(reader, ELEM_SEP);
707         }
708       }
709 
710     private:
711       bool first_;
712     }
713 
714     // Read 1 character from the transport trans and verify that it is the
715     // expected character ch.
716     // Throw a protocol exception if it is not.
717     void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
718       auto ch2 = reader.read();
719       if (ch2 != ch) {
720         throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
721           cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
722       }
723     }
724   }
725 
726   // Probably need to implement a better stack at some point.
727   Context[] contextStack_;
728   Context context_;
729 
730   Transport trans_;
731   LookaheadReader reader_;
732 }
733 
734 /**
735  * TJsonProtocol construction helper to avoid having to explicitly specify
736  * the transport type, i.e. to allow the constructor being called using IFTI
737  * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
738  * enhancement requet 6082)).
739  */
740 TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans,
741   int containerSizeLimit = 0, int stringSizeLimit = 0
742 ) if (isTTransport!Transport) {
743   return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit);
744 }
745 
746 unittest {
747   import std.exception;
748   import thrift.transport.memory;
749 
750   // Check the message header format.
751   auto buf = new TMemoryBuffer;
752   auto json = tJsonProtocol(buf);
753   json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
754   json.writeMessageEnd();
755 
756   auto header = new ubyte[13];
757   buf.readAll(header);
758   enforce(cast(char[])header == `[1,"foo",1,0]`);
759 }
760 
761 unittest {
762   import std.exception;
763   import thrift.transport.memory;
764 
765   // Check that short binary data is read correctly (the Thrift JSON format
766   // does not include padding chars in the Base64 encoded data).
767   auto buf = new TMemoryBuffer;
768   auto json = tJsonProtocol(buf);
769   json.writeBinary([1, 2]);
770   json.reset();
771   enforce(json.readBinary() == [1, 2]);
772 }
773 
774 unittest {
775   import thrift.internal.test.protocol;
776   testContainerSizeLimit!(TJsonProtocol!())();
777   testStringSizeLimit!(TJsonProtocol!())();
778 }
779 
780 /**
781  * TProtocolFactory creating a TJsonProtocol instance for passed in
782  * transports.
783  *
784  * The optional Transports template tuple parameter can be used to specify
785  * one or more TTransport implementations to specifically instantiate
786  * TJsonProtocol for. If the actual transport types encountered at
787  * runtime match one of the transports in the list, a specialized protocol
788  * instance is created. Otherwise, a generic TTransport version is used.
789  */
790 class TJsonProtocolFactory(Transports...) if (
791   allSatisfy!(isTTransport, Transports)
792 ) : TProtocolFactory {
793   TProtocol getProtocol(TTransport trans) const {
794     foreach (Transport; TypeTuple!(Transports, TTransport)) {
795       auto concreteTrans = cast(Transport)trans;
796       if (concreteTrans) {
797         auto p = new TJsonProtocol!Transport(concreteTrans);
798         return p;
799       }
800     }
801     throw new TProtocolException(
802       "Passed null transport to TJsonProtocolFactoy.");
803   }
804 }
805 
806 private {
807   immutable ubyte[1] OBJECT_BEGIN = '{';
808   immutable ubyte[1] OBJECT_END = '}';
809   immutable ubyte[1] ARRAY_BEGIN = '[';
810   immutable ubyte[1] ARRAY_END = ']';
811   immutable ubyte[1] NEWLINE = '\n';
812   immutable ubyte[1] PAIR_SEP = ':';
813   immutable ubyte[1] ELEM_SEP = ',';
814   immutable ubyte[1] BACKSLASH = '\\';
815   immutable ubyte[1] STRING_DELIMITER = '"';
816   immutable ubyte[1] ZERO_CHAR = '0';
817   immutable ubyte[1] ESCAPE_CHAR = 'u';
818   immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00";
819 
820   enum THRIFT_JSON_VERSION = 1;
821 
822   immutable NAN_STRING = "NaN";
823   immutable INFINITY_STRING = "Infinity";
824   immutable NEG_INFINITY_STRING = "-Infinity";
825 
826   string getNameFromTType(TType typeID) {
827     final switch (typeID) {
828       case TType.BOOL:
829         return "tf";
830       case TType.BYTE:
831         return "i8";
832       case TType.I16:
833         return "i16";
834       case TType.I32:
835         return "i32";
836       case TType.I64:
837         return "i64";
838       case TType.DOUBLE:
839         return "dbl";
840       case TType.STRING:
841         return "str";
842       case TType.STRUCT:
843         return "rec";
844       case TType.MAP:
845         return "map";
846       case TType.LIST:
847         return "lst";
848       case TType.SET:
849         return "set";
850       case TType.STOP: goto case;
851       case TType.VOID:
852         assert(false, "Invalid type passed.");
853     }
854   }
855 
856   TType getTTypeFromName(string name) {
857     TType result;
858     if (name.length > 1) {
859       switch (name[0]) {
860         case 'd':
861           result = TType.DOUBLE;
862           break;
863         case 'i':
864           switch (name[1]) {
865             case '8':
866               result = TType.BYTE;
867               break;
868             case '1':
869               result = TType.I16;
870               break;
871             case '3':
872               result = TType.I32;
873               break;
874             case '6':
875               result = TType.I64;
876               break;
877             default:
878               // Do nothing.
879           }
880           break;
881         case 'l':
882           result = TType.LIST;
883           break;
884         case 'm':
885           result = TType.MAP;
886           break;
887         case 'r':
888           result = TType.STRUCT;
889           break;
890         case 's':
891           if (name[1] == 't') {
892             result = TType.STRING;
893           }
894           else if (name[1] == 'e') {
895             result = TType.SET;
896           }
897           break;
898         case 't':
899           result = TType.BOOL;
900           break;
901         default:
902           // Do nothing.
903       }
904     }
905     if (result == TType.STOP) {
906       throw new TProtocolException("Unrecognized type",
907         TProtocolException.Type.NOT_IMPLEMENTED);
908     }
909     return result;
910   }
911 
912   // This table describes the handling for the first 0x30 characters
913   //  0 : escape using "\u00xx" notation
914   //  1 : just output index
915   // <other> : escape using "\<other>" notation
916   immutable ubyte[0x30] kJsonCharTable = [
917   //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
918       0,  0,  0,  0,  0,  0,  0,  0,'b','t','n',  0,'f','r',  0,  0, // 0
919       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 1
920       1,  1,'"',  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, // 2
921   ];
922 
923   // This string's characters must match up with the elements in kEscapeCharVals.
924   // I don't have '/' on this list even though it appears on www.json.org --
925   // it is not in the RFC
926   immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`;
927 
928   // The elements of this array must match up with the sequence of characters in
929   // kEscapeChars
930   immutable ubyte[7] kEscapeCharVals = [
931     '"', '\\', '\b', '\f', '\n', '\r', '\t',
932   ];
933 
934   // Return the integer value of a hex character ch.
935   // Throw a protocol exception if the character is not [0-9a-f].
936   ubyte hexVal(ubyte ch) {
937     if ((ch >= '0') && (ch <= '9')) {
938       return cast(ubyte)(ch - '0');
939     } else if ((ch >= 'a') && (ch <= 'f')) {
940       return cast(ubyte)(ch - 'a' + 10);
941     }
942     else {
943       throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~
944         ch ~ "'.", TProtocolException.Type.INVALID_DATA);
945     }
946   }
947 
948   // Return the hex character representing the integer val. The value is masked
949   // to make sure it is in the correct range.
950   ubyte hexChar(ubyte val) {
951     val &= 0x0F;
952     if (val < 10) {
953       return cast(ubyte)(val + '0');
954     } else {
955       return cast(ubyte)(val - 10 + 'a');
956     }
957   }
958 
959   // Return true if the character ch is in [-+0-9.Ee]; false otherwise
960   bool isJsonNumeric(ubyte ch) {
961     switch (ch) {
962       case '+':
963       case '-':
964       case '.':
965       case '0':
966       case '1':
967       case '2':
968       case '3':
969       case '4':
970       case '5':
971       case '6':
972       case '7':
973       case '8':
974       case '9':
975       case 'E':
976       case 'e':
977         return true;
978       default:
979         return false;
980     }
981   }
982 }