123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
/*
 * 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.json;

import std.algorithm;
import std.array;
import std.base64;
import std.conv;
import std.range;
import std.string : format;
import std.traits : isIntegral;
import std.typetuple : allSatisfy, TypeTuple;
import thrift.protocol.base;
import thrift.transport.base;

alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad;

/**
 * Implementation of the Thrift JSON protocol.
 */
final class TJsonProtocol(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.
   */
  this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
    trans_ = trans;
    this.containerSizeLimit = containerSizeLimit;
    this.stringSizeLimit = stringSizeLimit;

    context_ = new Context();
    reader_ = new LookaheadReader(trans);
  }

  Transport transport() @property {
    return trans_;
  }

  void reset() {
    contextStack_.clear();
    context_ = new Context();
    reader_ = new LookaheadReader(trans_);
  }

  /**
   * 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.
   *
   * Note: For binary data, the limit applies to the length of the
   * Base64-encoded string data, not the resulting byte array.
   *
   * Defaults to zero (no limit).
   */
  int stringSizeLimit;

  /*
   * Writing methods.
   */

  void writeBool(bool b) {
    writeJsonInteger(b ? 1 : 0);
  }

  void writeByte(byte b) {
    writeJsonInteger(b);
  }

  void writeI16(short i16) {
    writeJsonInteger(i16);
  }

  void writeI32(int i32) {
    writeJsonInteger(i32);
  }

  void writeI64(long i64) {
    writeJsonInteger(i64);
  }

  void writeDouble(double dub) {
    context_.write(trans_);

    string value;
    if (dub is double.nan) {
      value = NAN_STRING;
    } else if (dub is double.infinity) {
      value = INFINITY_STRING;
    } else if (dub is -double.infinity) {
      value = NEG_INFINITY_STRING;
    }

    bool escapeNum = value !is null || context_.escapeNum;

    if (value is null) {
      value = format("%.16g", dub);
    }

    if (escapeNum) trans_.write(STRING_DELIMITER);
    trans_.write(cast(ubyte[])value);
    if (escapeNum) trans_.write(STRING_DELIMITER);
  }

  void writeString(string str) {
    context_.write(trans_);
    trans_.write(STRING_DELIMITER);
    foreach (c; str) {
      writeJsonChar(c);
    }
    trans_.write(STRING_DELIMITER);
  }

  void writeBinary(ubyte[] buf) {
    context_.write(trans_);

    trans_.write(STRING_DELIMITER);
    ubyte[4] b;
    while (!buf.empty) {
      auto toWrite = take(buf, 3);
      Base64NoPad.encode(toWrite, b[]);
      trans_.write(b[0 .. toWrite.length + 1]);
      buf.popFrontN(toWrite.length);
    }
    trans_.write(STRING_DELIMITER);
  }

  void writeMessageBegin(TMessage msg) {
    writeJsonArrayBegin();
    writeJsonInteger(THRIFT_JSON_VERSION);
    writeString(msg.name);
    writeJsonInteger(cast(byte)msg.type);
    writeJsonInteger(msg.seqid);
  }

  void writeMessageEnd() {
    writeJsonArrayEnd();
  }

  void writeStructBegin(TStruct tstruct) {
    writeJsonObjectBegin();
  }

  void writeStructEnd() {
    writeJsonObjectEnd();
  }

  void writeFieldBegin(TField field) {
    writeJsonInteger(field.id);
    writeJsonObjectBegin();
    writeString(getNameFromTType(field.type));
  }

  void writeFieldEnd() {
    writeJsonObjectEnd();
  }

  void writeFieldStop() {}

  void writeListBegin(TList list) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(list.elemType));
    writeJsonInteger(list.size);
  }

  void writeListEnd() {
    writeJsonArrayEnd();
  }

  void writeMapBegin(TMap map) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(map.keyType));
    writeString(getNameFromTType(map.valueType));
    writeJsonInteger(map.size);
    writeJsonObjectBegin();
  }

  void writeMapEnd() {
    writeJsonObjectEnd();
    writeJsonArrayEnd();
  }

  void writeSetBegin(TSet set) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(set.elemType));
    writeJsonInteger(set.size);
  }

  void writeSetEnd() {
    writeJsonArrayEnd();
  }


  /*
   * Reading methods.
   */

  bool readBool() {
    return readJsonInteger!byte() ? true : false;
  }

  byte readByte() {
    return readJsonInteger!byte();
  }

  short readI16() {
    return readJsonInteger!short();
  }

  int readI32() {
    return readJsonInteger!int();
  }

  long readI64() {
    return readJsonInteger!long();
  }

  double readDouble() {
    context_.read(reader_);

    if (reader_.peek() == STRING_DELIMITER) {
      auto str = readJsonString(true);
      if (str == NAN_STRING) {
        return double.nan;
      }
      if (str == INFINITY_STRING) {
        return double.infinity;
      }
      if (str == NEG_INFINITY_STRING) {
        return -double.infinity;
      }

      if (!context_.escapeNum) {
        // Throw exception -- we should not be in a string in this case
        throw new TProtocolException("Numeric data unexpectedly quoted",
          TProtocolException.Type.INVALID_DATA);
      }
      try {
        return to!double(str);
      } catch (ConvException e) {
        throw new TProtocolException(`Expected numeric value; got "` ~ str ~
          `".`, TProtocolException.Type.INVALID_DATA);
      }
    }
    else {
      if (context_.escapeNum) {
        // This will throw - we should have had a quote if escapeNum == true
        readJsonSyntaxChar(STRING_DELIMITER);
      }

      auto str = readJsonNumericChars();
      try {
        return to!double(str);
      } catch (ConvException e) {
        throw new TProtocolException(`Expected numeric value; got "` ~ str ~
          `".`, TProtocolException.Type.INVALID_DATA);
      }
    }
  }

  string readString() {
    return readJsonString(false);
  }

  ubyte[] readBinary() {
    return Base64NoPad.decode(readString());
  }

  TMessage readMessageBegin() {
    TMessage msg = void;

    readJsonArrayBegin();

    auto ver = readJsonInteger!short();
    if (ver != THRIFT_JSON_VERSION) {
      throw new TProtocolException("Message contained bad version.",
        TProtocolException.Type.BAD_VERSION);
    }

    msg.name = readString();
    msg.type = cast(TMessageType)readJsonInteger!byte();
    msg.seqid = readJsonInteger!short();

    return msg;
  }

  void readMessageEnd() {
    readJsonArrayEnd();
  }

  TStruct readStructBegin() {
    readJsonObjectBegin();
    return TStruct();
  }

  void readStructEnd() {
    readJsonObjectEnd();
  }

  TField readFieldBegin() {
    TField f = void;
    f.name = null;

    auto ch = reader_.peek();
    if (ch == OBJECT_END) {
      f.type = TType.STOP;
    } else {
      f.id = readJsonInteger!short();
      readJsonObjectBegin();
      f.type = getTTypeFromName(readString());
    }

    return f;
  }

  void readFieldEnd() {
    readJsonObjectEnd();
  }

  TList readListBegin() {
    readJsonArrayBegin();
    auto type = getTTypeFromName(readString());
    auto size = readContainerSize();
    return TList(type, size);
  }

  void readListEnd() {
    readJsonArrayEnd();
  }

  TMap readMapBegin() {
    readJsonArrayBegin();
    auto keyType = getTTypeFromName(readString());
    auto valueType = getTTypeFromName(readString());
    auto size = readContainerSize();
    readJsonObjectBegin();
    return TMap(keyType, valueType, size);
  }

  void readMapEnd() {
    readJsonObjectEnd();
    readJsonArrayEnd();
  }

  TSet readSetBegin() {
    readJsonArrayBegin();
    auto type = getTTypeFromName(readString());
    auto size = readContainerSize();
    return TSet(type, size);
  }

  void readSetEnd() {
    readJsonArrayEnd();
  }

private:
  void pushContext(Context c) {
    contextStack_ ~= context_;
    context_ = c;
  }

  void popContext() {
    context_ = contextStack_.back;
    contextStack_.popBack();
    contextStack_.assumeSafeAppend();
  }

  /*
   * Writing functions
   */

  // Write the character ch as a Json escape sequence ("\u00xx")
  void writeJsonEscapeChar(ubyte ch) {
    trans_.write(ESCAPE_PREFIX);
    trans_.write(ESCAPE_PREFIX);
    auto outCh = hexChar(cast(ubyte)(ch >> 4));
    trans_.write((&outCh)[0 .. 1]);
    outCh = hexChar(ch);
    trans_.write((&outCh)[0 .. 1]);
  }

  // Write the character ch as part of a Json string, escaping as appropriate.
  void writeJsonChar(ubyte ch) {
    if (ch >= 0x30) {
      if (ch == '\\') { // Only special character >= 0x30 is '\'
        trans_.write(BACKSLASH);
        trans_.write(BACKSLASH);
      } else {
        trans_.write((&ch)[0 .. 1]);
      }
    }
    else {
      auto outCh = kJsonCharTable[ch];
      // Check if regular character, backslash escaped, or Json escaped
      if (outCh == 1) {
        trans_.write((&ch)[0 .. 1]);
      } else if (outCh > 1) {
        trans_.write(BACKSLASH);
        trans_.write((&outCh)[0 .. 1]);
      } else {
        writeJsonEscapeChar(ch);
      }
    }
  }

  // Convert the given integer type to a Json number, or a string
  // if the context requires it (eg: key in a map pair).
  void writeJsonInteger(T)(T num) if (isIntegral!T) {
    context_.write(trans_);

    auto escapeNum = context_.escapeNum();
    if (escapeNum) trans_.write(STRING_DELIMITER);
    trans_.write(cast(ubyte[])to!string(num));
    if (escapeNum) trans_.write(STRING_DELIMITER);
  }

  void writeJsonObjectBegin() {
    context_.write(trans_);
    trans_.write(OBJECT_BEGIN);
    pushContext(new PairContext());
  }

  void writeJsonObjectEnd() {
    popContext();
    trans_.write(OBJECT_END);
  }

  void writeJsonArrayBegin() {
    context_.write(trans_);
    trans_.write(ARRAY_BEGIN);
    pushContext(new ListContext());
  }

  void writeJsonArrayEnd() {
    popContext();
    trans_.write(ARRAY_END);
  }

  /*
   * Reading functions
   */

  int readContainerSize() {
    auto size = readJsonInteger!int();
    if (size < 0) {
      throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
    } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
      throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
    }
    return size;
  }

  void readJsonSyntaxChar(ubyte[1] ch) {
    return readSyntaxChar(reader_, ch);
  }

  ubyte readJsonEscapeChar() {
    readJsonSyntaxChar(ZERO_CHAR);
    readJsonSyntaxChar(ZERO_CHAR);
    auto a = reader_.read();
    auto b = reader_.read();
    return cast(ubyte)((hexVal(a[0]) << 4) + hexVal(b[0]));
  }

  string readJsonString(bool skipContext = false) {
    if (!skipContext) context_.read(reader_);

    readJsonSyntaxChar(STRING_DELIMITER);
    auto buffer = appender!string();

    int bytesRead;
    while (true) {
      auto ch = reader_.read();
      if (ch == STRING_DELIMITER) {
        break;
      }

      ++bytesRead;
      if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
        throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
      }

      if (ch == BACKSLASH) {
        ch = reader_.read();
        if (ch == ESCAPE_CHAR) {
          ch = readJsonEscapeChar();
        } else {
          auto pos = countUntil(kEscapeChars[], ch[0]);
          if (pos == -1) {
            throw new TProtocolException("Expected control char, got '" ~
              cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
          }
          ch = kEscapeCharVals[pos];
        }
      }
      buffer.put(ch[0]);
    }

    return buffer.data;
  }

  // Reads a sequence of characters, stopping at the first one that is not
  // a valid Json numeric character.
  string readJsonNumericChars() {
    string str;
    while (true) {
      auto ch = reader_.peek();
      if (!isJsonNumeric(ch[0])) {
        break;
      }
      reader_.read();
      str ~= ch;
    }
    return str;
  }

  // Reads a sequence of characters and assembles them into a number,
  // returning them via num
  T readJsonInteger(T)() if (isIntegral!T) {
    context_.read(reader_);
    if (context_.escapeNum()) {
      readJsonSyntaxChar(STRING_DELIMITER);
    }
    auto str = readJsonNumericChars();
    T num;
    try {
      num = to!T(str);
    } catch (ConvException e) {
      throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
        TProtocolException.Type.INVALID_DATA);
    }
    if (context_.escapeNum()) {
      readJsonSyntaxChar(STRING_DELIMITER);
    }
    return num;
  }

  void readJsonObjectBegin() {
    context_.read(reader_);
    readJsonSyntaxChar(OBJECT_BEGIN);
    pushContext(new PairContext());
  }

  void readJsonObjectEnd() {
    readJsonSyntaxChar(OBJECT_END);
    popContext();
  }

  void readJsonArrayBegin() {
    context_.read(reader_);
    readJsonSyntaxChar(ARRAY_BEGIN);
    pushContext(new ListContext());
  }

  void readJsonArrayEnd() {
    readJsonSyntaxChar(ARRAY_END);
    popContext();
  }

  static {
    final class LookaheadReader {
      this(Transport trans) {
        trans_ = trans;
      }

      ubyte[1] read() {
        if (hasData_) {
          hasData_ = false;
        } else {
          trans_.readAll(data_);
        }
        return data_;
      }

      ubyte[1] peek() {
        if (!hasData_) {
          trans_.readAll(data_);
          hasData_ = true;
        }
        return data_;
      }

     private:
      Transport trans_;
      bool hasData_;
      ubyte[1] data_;
    }

    /*
     * Class to serve as base Json context and as base class for other context
     * implementations
     */
    class Context {
      /**
       * Write context data to the transport. Default is to do nothing.
       */
      void write(Transport trans) {}

      /**
       * Read context data from the transport. Default is to do nothing.
       */
      void read(LookaheadReader reader) {}

      /**
       * Return true if numbers need to be escaped as strings in this context.
       * Default behavior is to return false.
       */
      bool escapeNum() @property {
        return false;
      }
    }

    // Context class for object member key-value pairs
    class PairContext : Context {
      this() {
        first_ = true;
        colon_ = true;
      }

      override void write(Transport trans) {
        if (first_) {
          first_ = false;
          colon_ = true;
        } else {
          trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
          colon_ = !colon_;
        }
      }

      override void read(LookaheadReader reader) {
        if (first_) {
          first_ = false;
          colon_ = true;
        } else {
          auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
          colon_ = !colon_;
          return readSyntaxChar(reader, ch);
        }
      }

      // Numbers must be turned into strings if they are the key part of a pair
      override bool escapeNum() @property {
        return colon_;
      }

    private:
      bool first_;
      bool colon_;
    }

    class ListContext : Context {
      this() {
        first_ = true;
      }

      override void write(Transport trans) {
        if (first_) {
          first_ = false;
        } else {
          trans.write(ELEM_SEP);
        }
      }

      override void read(LookaheadReader reader) {
        if (first_) {
          first_ = false;
        } else {
          readSyntaxChar(reader, ELEM_SEP);
        }
      }

    private:
      bool first_;
    }

    // Read 1 character from the transport trans and verify that it is the
    // expected character ch.
    // Throw a protocol exception if it is not.
    void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
      auto ch2 = reader.read();
      if (ch2 != ch) {
        throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
          cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
      }
    }
  }

  // Probably need to implement a better stack at some point.
  Context[] contextStack_;
  Context context_;

  Transport trans_;
  LookaheadReader reader_;
}

/**
 * TJsonProtocol 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)).
 */
TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans,
  int containerSizeLimit = 0, int stringSizeLimit = 0
) if (isTTransport!Transport) {
  return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit);
}

unittest {
  import std.exception;
  import thrift.transport.memory;

  // Check the message header format.
  auto buf = new TMemoryBuffer;
  auto json = tJsonProtocol(buf);
  json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
  json.writeMessageEnd();

  auto header = new ubyte[13];
  buf.readAll(header);
  enforce(cast(char[])header == `[1,"foo",1,0]`);
}

unittest {
  import std.exception;
  import thrift.transport.memory;

  // Check that short binary data is read correctly (the Thrift JSON format
  // does not include padding chars in the Base64 encoded data).
  auto buf = new TMemoryBuffer;
  auto json = tJsonProtocol(buf);
  json.writeBinary([1, 2]);
  json.reset();
  enforce(json.readBinary() == [1, 2]);
}

unittest {
  import thrift.internal.test.protocol;
  testContainerSizeLimit!(TJsonProtocol!())();
  testStringSizeLimit!(TJsonProtocol!())();
}

/**
 * TProtocolFactory creating a TJsonProtocol instance for passed in
 * transports.
 *
 * The optional Transports template tuple parameter can be used to specify
 * one or more TTransport implementations to specifically instantiate
 * TJsonProtocol 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 TJsonProtocolFactory(Transports...) if (
  allSatisfy!(isTTransport, Transports)
) : TProtocolFactory {
  TProtocol getProtocol(TTransport trans) const {
    foreach (Transport; TypeTuple!(Transports, TTransport)) {
      auto concreteTrans = cast(Transport)trans;
      if (concreteTrans) {
        auto p = new TJsonProtocol!Transport(concreteTrans);
        return p;
      }
    }
    throw new TProtocolException(
      "Passed null transport to TJsonProtocolFactoy.");
  }
}

private {
  immutable ubyte[1] OBJECT_BEGIN = '{';
  immutable ubyte[1] OBJECT_END = '}';
  immutable ubyte[1] ARRAY_BEGIN = '[';
  immutable ubyte[1] ARRAY_END = ']';
  immutable ubyte[1] NEWLINE = '\n';
  immutable ubyte[1] PAIR_SEP = ':';
  immutable ubyte[1] ELEM_SEP = ',';
  immutable ubyte[1] BACKSLASH = '\\';
  immutable ubyte[1] STRING_DELIMITER = '"';
  immutable ubyte[1] ZERO_CHAR = '0';
  immutable ubyte[1] ESCAPE_CHAR = 'u';
  immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00";

  enum THRIFT_JSON_VERSION = 1;

  immutable NAN_STRING = "NaN";
  immutable INFINITY_STRING = "Infinity";
  immutable NEG_INFINITY_STRING = "-Infinity";

  string getNameFromTType(TType typeID) {
    final switch (typeID) {
      case TType.BOOL:
        return "tf";
      case TType.BYTE:
        return "i8";
      case TType.I16:
        return "i16";
      case TType.I32:
        return "i32";
      case TType.I64:
        return "i64";
      case TType.DOUBLE:
        return "dbl";
      case TType.STRING:
        return "str";
      case TType.STRUCT:
        return "rec";
      case TType.MAP:
        return "map";
      case TType.LIST:
        return "lst";
      case TType.SET:
        return "set";
    }
  }

  TType getTTypeFromName(string name) {
    TType result;
    if (name.length > 1) {
      switch (name[0]) {
        case 'd':
          result = TType.DOUBLE;
          break;
        case 'i':
          switch (name[1]) {
            case '8':
              result = TType.BYTE;
              break;
            case '1':
              result = TType.I16;
              break;
            case '3':
              result = TType.I32;
              break;
            case '6':
              result = TType.I64;
              break;
            default:
              // Do nothing.
          }
          break;
        case 'l':
          result = TType.LIST;
          break;
        case 'm':
          result = TType.MAP;
          break;
        case 'r':
          result = TType.STRUCT;
          break;
        case 's':
          if (name[1] == 't') {
            result = TType.STRING;
          }
          else if (name[1] == 'e') {
            result = TType.SET;
          }
          break;
        case 't':
          result = TType.BOOL;
          break;
        default:
          // Do nothing.
      }
    }
    if (result == TType.STOP) {
      throw new TProtocolException("Unrecognized type",
        TProtocolException.Type.NOT_IMPLEMENTED);
    }
    return result;
  }

  // This table describes the handling for the first 0x30 characters
  //  0 : escape using "\u00xx" notation
  //  1 : just output index
  // <other> : escape using "\<other>" notation
  immutable ubyte[0x30] kJsonCharTable = [
  //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
      0,  0,  0,  0,  0,  0,  0,  0,'b','t','n',  0,'f','r',  0,  0, // 0
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 1
      1,  1,'"',  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, // 2
  ];

  // This string's characters must match up with the elements in kEscapeCharVals.
  // I don't have '/' on this list even though it appears on www.json.org --
  // it is not in the RFC
  immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`;

  // The elements of this array must match up with the sequence of characters in
  // kEscapeChars
  immutable ubyte[7] kEscapeCharVals = [
    '"', '\\', '\b', '\f', '\n', '\r', '\t',
  ];

  // Return the integer value of a hex character ch.
  // Throw a protocol exception if the character is not [0-9a-f].
  ubyte hexVal(ubyte ch) {
    if ((ch >= '0') && (ch <= '9')) {
      return cast(ubyte)(ch - '0');
    } else if ((ch >= 'a') && (ch <= 'f')) {
      return cast(ubyte)(ch - 'a' + 10);
    }
    else {
      throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~
        ch ~ "'.", TProtocolException.Type.INVALID_DATA);
    }
  }

  // Return the hex character representing the integer val. The value is masked
  // to make sure it is in the correct range.
  ubyte hexChar(ubyte val) {
    val &= 0x0F;
    if (val < 10) {
      return cast(ubyte)(val + '0');
    } else {
      return cast(ubyte)(val - 10 + 'a');
    }
  }

  // Return true if the character ch is in [-+0-9.Ee]; false otherwise
  bool isJsonNumeric(ubyte ch) {
    switch (ch) {
      case '+':
      case '-':
      case '.':
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case 'E':
      case 'e':
        return true;
      default:
        return false;
    }
  }
}