THRIFT-3299 Create an Apache Thrift language binding for Dart (dartlang.org).
Client: Dart
Patch: Mark Erickson <mark.erickson@workiva.com>

This closes #608
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5066a00..44bb9ff 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -65,6 +65,10 @@
 SUBDIRS += php
 endif
 
+if WITH_DART
+SUBDIRS += dart
+endif
+
 if WITH_GO
 SUBDIRS += go
 endif
@@ -88,6 +92,7 @@
 	as3 \
 	cocoa \
 	d \
+	dart \
 	delphi \
 	haxe \
 	javame \
diff --git a/lib/dart/LICENSE_HEADER b/lib/dart/LICENSE_HEADER
new file mode 100644
index 0000000..4eacb64
--- /dev/null
+++ b/lib/dart/LICENSE_HEADER
@@ -0,0 +1,16 @@
+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.
diff --git a/lib/dart/Makefile.am b/lib/dart/Makefile.am
new file mode 100644
index 0000000..6dfff40
--- /dev/null
+++ b/lib/dart/Makefile.am
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+all-local:
+	$(DARTPUB) get
+
+clean-local:
+	$(RM) -r .pub
+
+check-local: all
diff --git a/lib/dart/README.md b/lib/dart/README.md
new file mode 100644
index 0000000..2be168b
--- /dev/null
+++ b/lib/dart/README.md
@@ -0,0 +1,26 @@
+Thrift Dart Library
+
+License
+=======
+
+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.
+
+Using Thrift with Dart
+====================
+
+Dart 1.12.0 or newer is required
diff --git a/lib/dart/coding_standards.md b/lib/dart/coding_standards.md
new file mode 100644
index 0000000..62f6003
--- /dev/null
+++ b/lib/dart/coding_standards.md
@@ -0,0 +1,6 @@
+# Dart Coding Standards
+
+### Please follow:
+ * [Thrift General Coding Standards](/doc/coding_standards.md)
+ * [Use dartfmt](https://www.dartlang.org/tools/dartfmt/) and follow the 
+   [Dart Style Guide](https://www.dartlang.org/articles/style-guide/)
diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart
new file mode 100644
index 0000000..dfcee83
--- /dev/null
+++ b/lib/dart/lib/src/browser/t_web_socket.dart
@@ -0,0 +1,130 @@
+/// 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.
+
+library thrift.src.browser;
+
+import 'dart:async';
+import 'dart:html' show CloseEvent;
+import 'dart:html' show Event;
+import 'dart:html' show MessageEvent;
+import 'dart:html' show WebSocket;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [WebSocket] from dart:html
+class TWebSocket implements TSocket {
+  final Uri url;
+
+  final StreamController<TSocketState> _onStateController;
+  Stream<TSocketState> get onState => _onStateController.stream;
+
+  final StreamController<Object> _onErrorController;
+  Stream<Object> get onError => _onErrorController.stream;
+
+  final StreamController<Uint8List> _onMessageController;
+  Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+  final List<Uint8List> _requests = [];
+
+  TWebSocket(this.url)
+      : _onStateController = new StreamController.broadcast(),
+        _onErrorController = new StreamController.broadcast(),
+        _onMessageController = new StreamController.broadcast() {
+    if (url == null || !url.hasAuthority || !url.hasPort) {
+      throw new ArgumentError('Invalid url');
+    }
+  }
+
+  WebSocket _socket;
+
+  bool get isOpen => _socket != null && _socket.readyState == WebSocket.OPEN;
+
+  bool get isClosed =>
+      _socket == null || _socket.readyState == WebSocket.CLOSED;
+
+  Future open() {
+    if (!isClosed) {
+      throw new TTransportError(
+          TTransportErrorType.ALREADY_OPEN, 'Socket already connected');
+    }
+
+    _socket = new WebSocket(url.toString());
+    _socket.onError.listen(_onError);
+    _socket.onOpen.listen(_onOpen);
+    _socket.onClose.listen(_onClose);
+    _socket.onMessage.listen(_onMessage);
+
+    return _socket.onOpen.first;
+  }
+
+  Future close() {
+    if (_socket != null) {
+      _socket.close();
+      return _socket.onClose.first;
+    } else {
+      return new Future.value();
+    }
+  }
+
+  void send(Uint8List data) {
+    _requests.add(data);
+    _sendRequests();
+  }
+
+  void _sendRequests() {
+    while (isOpen && _requests.isNotEmpty) {
+      Uint8List data = _requests.removeAt(0);
+      _socket.sendString(CryptoUtils.bytesToBase64(data));
+    }
+  }
+
+  void _onOpen(Event event) {
+    _onStateController.add(TSocketState.OPEN);
+    _sendRequests();
+  }
+
+  void _onClose(CloseEvent event) {
+    _socket = null;
+
+    if (_requests.isNotEmpty) {
+      _onErrorController
+          .add(new StateError('Socket was closed with pending requests'));
+    }
+    _requests.clear();
+
+    _onStateController.add(TSocketState.CLOSED);
+  }
+
+  void _onMessage(MessageEvent message) {
+    try {
+      Uint8List data =
+          new Uint8List.fromList(CryptoUtils.base64StringToBytes(message.data));
+      _onMessageController.add(data);
+    } on FormatException catch (_) {
+      var error = new TProtocolError(TProtocolErrorType.INVALID_DATA,
+          "Expected a Base 64 encoded string.");
+      _onErrorController.add(error);
+    }
+  }
+
+  void _onError(Event event) {
+    close();
+    _onErrorController.add(event.toString());
+  }
+}
diff --git a/lib/dart/lib/src/console/t_tcp_socket.dart b/lib/dart/lib/src/console/t_tcp_socket.dart
new file mode 100644
index 0000000..b714803
--- /dev/null
+++ b/lib/dart/lib/src/console/t_tcp_socket.dart
@@ -0,0 +1,81 @@
+/// 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.
+
+library thrift.src.console.t_tcp_socket;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [Socket] from dart:io
+class TTcpSocket implements TSocket {
+  final StreamController<TSocketState> _onStateController;
+  Stream<TSocketState> get onState => _onStateController.stream;
+
+  final StreamController<Object> _onErrorController;
+  Stream<Object> get onError => _onErrorController.stream;
+
+  final StreamController<Uint8List> _onMessageController;
+  Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+  TTcpSocket(Socket socket)
+      : _onStateController = new StreamController.broadcast(),
+        _onErrorController = new StreamController.broadcast(),
+        _onMessageController = new StreamController.broadcast() {
+    if (socket == null) {
+      throw new ArgumentError.notNull('socket');
+    }
+
+    _socket = socket;
+    _socket.listen(_onMessage, onError: _onError, onDone: close);
+  }
+
+  Socket _socket;
+
+  bool get isOpen => _socket != null;
+
+  bool get isClosed => _socket == null;
+
+  Future open() async {
+    _onStateController.add(TSocketState.OPEN);
+  }
+
+  Future close() async {
+    if (_socket != null) {
+      await _socket.close();
+      _socket = null;
+    }
+
+    _onStateController.add(TSocketState.CLOSED);
+  }
+
+  void send(Uint8List data) {
+    _socket.add(data);
+  }
+
+  void _onMessage(List<int> message) {
+    Uint8List data = new Uint8List.fromList(message);
+    _onMessageController.add(data);
+  }
+
+  void _onError(Object error) {
+    close();
+    _onErrorController.add('$error');
+  }
+}
diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart
new file mode 100644
index 0000000..5a549be
--- /dev/null
+++ b/lib/dart/lib/src/console/t_web_socket.dart
@@ -0,0 +1,89 @@
+/// 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.
+
+library thrift.src.console.t_web_socket;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [WebSocket] from dart:io
+class TWebSocket implements TSocket {
+  final StreamController<TSocketState> _onStateController;
+  Stream<TSocketState> get onState => _onStateController.stream;
+
+  final StreamController<Object> _onErrorController;
+  Stream<Object> get onError => _onErrorController.stream;
+
+  final StreamController<Uint8List> _onMessageController;
+  Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+  TWebSocket(WebSocket socket)
+      : _onStateController = new StreamController.broadcast(),
+        _onErrorController = new StreamController.broadcast(),
+        _onMessageController = new StreamController.broadcast() {
+    if (socket == null) {
+      throw new ArgumentError.notNull('socket');
+    }
+
+    _socket = socket;
+    _socket.listen(_onMessage, onError: _onError, onDone: close);
+  }
+
+  WebSocket _socket;
+
+  bool get isOpen => _socket != null;
+
+  bool get isClosed => _socket == null;
+
+  Future open() async {
+    _onStateController.add(TSocketState.OPEN);
+  }
+
+  Future close() async {
+    if (_socket != null) {
+      await _socket.close();
+      _socket = null;
+    }
+
+    _onStateController.add(TSocketState.CLOSED);
+  }
+
+  void send(Uint8List data) {
+    _socket.add(CryptoUtils.bytesToBase64(data));
+  }
+
+  void _onMessage(String message) {
+    try {
+      Uint8List data =
+          new Uint8List.fromList(CryptoUtils.base64StringToBytes(message));
+      _onMessageController.add(data);
+    } on FormatException catch (_) {
+      var error = new TProtocolError(TProtocolErrorType.INVALID_DATA,
+          "Expected a Base 64 encoded string.");
+      _onErrorController.add(error);
+    }
+  }
+
+  void _onError(Object error) {
+    close();
+    _onErrorController.add('$error');
+  }
+}
diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart
new file mode 100644
index 0000000..f73223c
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart
@@ -0,0 +1,276 @@
+/// 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.
+
+part of thrift;
+
+class TBinaryProtocolFactory implements TProtocolFactory<TBinaryProtocol> {
+  TBinaryProtocolFactory({this.strictRead: false, this.strictWrite: true});
+
+  final bool strictRead;
+  final bool strictWrite;
+
+  TBinaryProtocol getProtocol(TTransport transport) {
+    return new TBinaryProtocol(transport,
+        strictRead: strictRead, strictWrite: strictWrite);
+  }
+}
+
+/// Binary protocol implementation for Thrift.
+///
+/// Adapted from the C# version.
+class TBinaryProtocol extends TProtocol {
+  static const int VERSION_MASK = 0xffff0000;
+  static const int VERSION_1 = 0x80010000;
+
+  static const Utf8Codec _utf8Codec = const Utf8Codec();
+
+  final bool strictRead;
+  final bool strictWrite;
+
+  TBinaryProtocol(TTransport transport,
+      {this.strictRead: false, this.strictWrite: true})
+      : super(transport);
+
+  /// write
+  void writeMessageBegin(TMessage message) {
+    if (strictWrite) {
+      int version = VERSION_1 | message.type;
+      writeI32(version);
+      writeString(message.name);
+      writeI32(message.seqid);
+    } else {
+      writeString(message.name);
+      writeByte(message.type);
+      writeI32(message.seqid);
+    }
+  }
+
+  void writeMessageEnd() {}
+
+  void writeStructBegin(TStruct struct) {}
+
+  void writeStructEnd() {}
+
+  void writeFieldBegin(TField field) {
+    writeByte(field.type);
+    writeI16(field.id);
+  }
+
+  void writeFieldEnd() {}
+
+  void writeFieldStop() {
+    writeByte(TType.STOP);
+  }
+
+  void writeMapBegin(TMap map) {
+    writeByte(map.keyType);
+    writeByte(map.valueType);
+    writeI32(map.length);
+  }
+
+  void writeMapEnd() {}
+
+  void writeListBegin(TList list) {
+    writeByte(list.elementType);
+    writeI32(list.length);
+  }
+
+  void writeListEnd() {}
+
+  void writeSetBegin(TSet set) {
+    writeByte(set.elementType);
+    writeI32(set.length);
+  }
+
+  void writeSetEnd() {}
+
+  void writeBool(bool b) {
+    if (b == null) b = false;
+    writeByte(b ? 1 : 0);
+  }
+
+  final ByteData _byteOut = new ByteData(1);
+  void writeByte(int byte) {
+    if (byte == null) byte = 0;
+    _byteOut.setUint8(0, byte);
+    transport.write(_byteOut.buffer.asUint8List(), 0, 1);
+  }
+
+  final ByteData _i16Out = new ByteData(2);
+  void writeI16(int i16) {
+    if (i16 == null) i16 = 0;
+    _i16Out.setInt16(0, i16);
+    transport.write(_i16Out.buffer.asUint8List(), 0, 2);
+  }
+
+  final ByteData _i32Out = new ByteData(4);
+  void writeI32(int i32) {
+    if (i32 == null) i32 = 0;
+    _i32Out.setInt32(0, i32);
+    transport.write(_i32Out.buffer.asUint8List(), 0, 4);
+  }
+
+  final ByteData _i64Out = new ByteData(8);
+  void writeI64(int i64) {
+    if (i64 == null) i64 = 0;
+    _i64Out.setInt64(0, i64);
+    transport.write(_i64Out.buffer.asUint8List(), 0, 8);
+  }
+
+  void writeString(String s) {
+    var bytes = s != null ? _utf8Codec.encode(s) : new Uint8List.fromList([]);
+    writeI32(bytes.length);
+    transport.write(bytes, 0, bytes.length);
+  }
+
+  final ByteData _doubleOut = new ByteData(8);
+  void writeDouble(double d) {
+    if (d == null) d = 0.0;
+    _doubleOut.setFloat64(0, d);
+    transport.write(_doubleOut.buffer.asUint8List(), 0, 8);
+  }
+
+  void writeBinary(Uint8List bytes) {
+    var length = bytes.length;
+    writeI32(length);
+    transport.write(bytes, 0, length);
+  }
+
+  /// read
+  TMessage readMessageBegin() {
+    String name;
+    int type;
+    int seqid;
+
+    int size = readI32();
+    if (size < 0) {
+      int version = size & VERSION_MASK;
+      if (version != VERSION_1) {
+        throw new TProtocolError(TProtocolErrorType.BAD_VERSION,
+            "Bad version in readMessageBegin: $version");
+      }
+      type = size & 0x000000ff;
+      name = readString();
+      seqid = readI32();
+    } else {
+      if (strictRead) {
+        throw new TProtocolError(TProtocolErrorType.BAD_VERSION,
+            "Missing version in readMessageBegin");
+      }
+      name = _readString(size);
+      type = readByte();
+      seqid = readI32();
+    }
+    return new TMessage(name, type, seqid);
+  }
+
+  void readMessageEnd() {}
+
+  TStruct readStructBegin() {
+    return new TStruct();
+  }
+
+  void readStructEnd() {}
+
+  TField readFieldBegin() {
+    String name = "";
+    int type = readByte();
+    int id = type != TType.STOP ? readI16() : 0;
+
+    return new TField(name, type, id);
+  }
+
+  void readFieldEnd() {}
+
+  TMap readMapBegin() {
+    int keyType = readByte();
+    int valueType = readByte();
+    int length = readI32();
+
+    return new TMap(keyType, valueType, length);
+  }
+
+  void readMapEnd() {}
+
+  TList readListBegin() {
+    int elementType = readByte();
+    int length = readI32();
+
+    return new TList(elementType, length);
+  }
+
+  void readListEnd() {}
+
+  TSet readSetBegin() {
+    int elementType = readByte();
+    int length = readI32();
+
+    return new TSet(elementType, length);
+  }
+
+  void readSetEnd() {}
+
+  bool readBool() => readByte() == 1;
+
+  final Uint8List _byteIn = new Uint8List(1);
+  int readByte() {
+    transport.readAll(_byteIn, 0, 1);
+    return _byteIn.buffer.asByteData().getUint8(0);
+  }
+
+  final Uint8List _i16In = new Uint8List(2);
+  int readI16() {
+    transport.readAll(_i16In, 0, 2);
+    return _i16In.buffer.asByteData().getInt16(0);
+  }
+
+  final Uint8List _i32In = new Uint8List(4);
+  int readI32() {
+    transport.readAll(_i32In, 0, 4);
+    return _i32In.buffer.asByteData().getInt32(0);
+  }
+
+  final Uint8List _i64In = new Uint8List(8);
+  int readI64() {
+    transport.readAll(_i64In, 0, 8);
+    return _i64In.buffer.asByteData().getInt64(0);
+  }
+
+  final Uint8List _doubleIn = new Uint8List(8);
+  double readDouble() {
+    transport.readAll(_doubleIn, 0, 8);
+    return _doubleIn.buffer.asByteData().getFloat64(0);
+  }
+
+  String readString() {
+    int size = readI32();
+    return _readString(size);
+  }
+
+  String _readString(int size) {
+    Uint8List stringIn = new Uint8List(size);
+    transport.readAll(stringIn, 0, size);
+    return _utf8Codec.decode(stringIn);
+  }
+
+  Uint8List readBinary() {
+    int length = readI32();
+    Uint8List binaryIn = new Uint8List(length);
+    transport.readAll(binaryIn, 0, length);
+    return binaryIn;
+  }
+}
diff --git a/lib/dart/lib/src/protocol/t_field.dart b/lib/dart/lib/src/protocol/t_field.dart
new file mode 100644
index 0000000..444b4e5
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_field.dart
@@ -0,0 +1,26 @@
+/// 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.
+
+part of thrift;
+
+class TField {
+  final String name;
+  final int type;
+  final int id;
+
+  TField(this.name, this.type, this.id);
+}
diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart
new file mode 100644
index 0000000..4fa6499
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_json_protocol.dart
@@ -0,0 +1,754 @@
+/// 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.
+
+part of thrift;
+
+class TJsonProtocolFactory implements TProtocolFactory<TJsonProtocol> {
+  TJsonProtocol getProtocol(TTransport transport) {
+    return new TJsonProtocol(transport);
+  }
+}
+
+/// JSON protocol implementation for Thrift.
+///
+/// Adapted from the C# version.
+class TJsonProtocol extends TProtocol {
+  static const int VERSION_1 = 1;
+
+  static const Utf8Codec utf8Codec = const Utf8Codec();
+
+  _BaseContext _context;
+  _BaseContext _rootContext;
+  _LookaheadReader _reader;
+
+  final List<_BaseContext> _contextStack = [];
+  final Uint8List _tempBuffer = new Uint8List(4);
+
+  TJsonProtocol(TTransport transport) : super(transport) {
+    _rootContext = new _BaseContext(this);
+    _reader = new _LookaheadReader(this);
+  }
+
+  void _pushContext(_BaseContext c) {
+    _contextStack.add(c);
+    _context = c;
+  }
+
+  void _popContext() {
+    _contextStack.removeLast();
+    _context = _contextStack.isEmpty ? _rootContext : _contextStack.last;
+  }
+
+  void _resetContext() {
+    _contextStack.clear();
+    _context = _rootContext;
+  }
+
+  /// Read a byte that must match [char]; otherwise throw a [TProtocolError].
+  void _readJsonSyntaxChar(int charByte) {
+    int byte = _reader.read();
+    if (byte != charByte) {
+      throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+          "Expected character ${new String.fromCharCode(charByte)} but found: ${new String.fromCharCode(byte)}");
+    }
+  }
+
+  int _hexVal(int byte) {
+    if (byte >= _Constants.HEX_0_BYTES[0] &&
+        byte <= _Constants.HEX_9_BYTES[0]) {
+      return byte - _Constants.HEX_0_BYTES[0];
+    } else if (byte >= _Constants.HEX_A_BYTES[0] &&
+        byte <= _Constants.HEX_F_BYTES[0]) {
+      byte += 10;
+      return byte - _Constants.HEX_A_BYTES[0];
+    } else {
+      throw new TProtocolError(
+          TProtocolErrorType.INVALID_DATA, "Expected hex character");
+    }
+  }
+
+  int _hexChar(int byte) => byte.toRadixString(16).codeUnitAt(0);
+
+  /// write
+
+  /// Write the [bytes] as JSON characters, escaping as needed.
+  void _writeJsonString(Uint8List bytes) {
+    _context.write();
+    transport.writeAll(_Constants.QUOTE_BYTES);
+
+    int length = bytes.length;
+    for (int i = 0; i < length; i++) {
+      int byte = bytes[i];
+      if ((byte & 0x00FF) >= 0x30) {
+        if (byte == _Constants.BACKSLASH_BYTES[0]) {
+          transport.writeAll(_Constants.BACKSLASH_BYTES);
+          transport.writeAll(_Constants.BACKSLASH_BYTES);
+        } else {
+          transport.write(bytes, i, 1);
+        }
+      } else {
+        _tempBuffer[0] = _Constants.JSON_CHAR_TABLE[byte];
+        if (_tempBuffer[0] == 1) {
+          transport.write(bytes, i, 1);
+        } else if (_tempBuffer[0] > 1) {
+          transport.writeAll(_Constants.BACKSLASH_BYTES);
+          transport.write(_tempBuffer, 0, 1);
+        } else {
+          transport.writeAll(_Constants.ESCSEQ_BYTES);
+          _tempBuffer[0] = _hexChar(byte >> 4);
+          _tempBuffer[1] = _hexChar(byte);
+          transport.write(_tempBuffer, 0, 2);
+        }
+      }
+    }
+
+    transport.writeAll(_Constants.QUOTE_BYTES);
+  }
+
+  void _writeJsonInteger(int i) {
+    if (i == null) i = 0;
+
+    _context.write();
+    String str = i.toString();
+
+    if (_context.escapeNumbers) {
+      transport.writeAll(_Constants.QUOTE_BYTES);
+    }
+    transport.writeAll(utf8Codec.encode(str));
+    if (_context.escapeNumbers) {
+      transport.writeAll(_Constants.QUOTE_BYTES);
+    }
+  }
+
+  void _writeJsonDouble(double d) {
+    if (d == null) d = 0.0;
+
+    _context.write();
+    String str = d.toString();
+    bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers;
+
+    if (escapeNumbers) {
+      transport.writeAll(_Constants.QUOTE_BYTES);
+    }
+    transport.writeAll(utf8Codec.encode(str));
+    if (escapeNumbers) {
+      transport.writeAll(_Constants.QUOTE_BYTES);
+    }
+  }
+
+  void _writeJsonBase64(Uint8List bytes) {
+    _context.write();
+    transport.writeAll(_Constants.QUOTE_BYTES);
+
+    String base64 = CryptoUtils.bytesToBase64(bytes);
+    transport.writeAll(utf8Codec.encode(base64));
+
+    transport.writeAll(_Constants.QUOTE_BYTES);
+  }
+
+  void _writeJsonObjectStart() {
+    _context.write();
+    transport.writeAll(_Constants.LBRACE_BYTES);
+    _pushContext(new _PairContext(this));
+  }
+
+  void _writeJsonObjectEnd() {
+    _popContext();
+    transport.writeAll(_Constants.RBRACE_BYTES);
+  }
+
+  void _writeJsonArrayStart() {
+    _context.write();
+    transport.writeAll(_Constants.LBRACKET_BYTES);
+    _pushContext(new _ListContext(this));
+  }
+
+  void _writeJsonArrayEnd() {
+    _popContext();
+    transport.writeAll(_Constants.RBRACKET_BYTES);
+  }
+
+  void writeMessageBegin(TMessage message) {
+    _resetContext();
+
+    _writeJsonArrayStart();
+    _writeJsonInteger(VERSION_1);
+
+    _writeJsonString(utf8Codec.encode(message.name));
+    _writeJsonInteger(message.type);
+    _writeJsonInteger(message.seqid);
+  }
+
+  void writeMessageEnd() {
+    _writeJsonArrayEnd();
+  }
+
+  void writeStructBegin(TStruct struct) {
+    _writeJsonObjectStart();
+  }
+
+  void writeStructEnd() {
+    _writeJsonObjectEnd();
+  }
+
+  void writeFieldBegin(TField field) {
+    _writeJsonInteger(field.id);
+    _writeJsonObjectStart();
+    _writeJsonString(_Constants.getTypeNameBytesForTypeId(field.type));
+  }
+
+  void writeFieldEnd() {
+    _writeJsonObjectEnd();
+  }
+
+  void writeFieldStop() {}
+
+  void writeMapBegin(TMap map) {
+    _writeJsonArrayStart();
+    _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.keyType));
+    _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.valueType));
+    _writeJsonInteger(map.length);
+    _writeJsonObjectStart();
+  }
+
+  void writeMapEnd() {
+    _writeJsonObjectEnd();
+    _writeJsonArrayEnd();
+  }
+
+  void writeListBegin(TList list) {
+    _writeJsonArrayStart();
+    _writeJsonString(_Constants.getTypeNameBytesForTypeId(list.elementType));
+    _writeJsonInteger(list.length);
+  }
+
+  void writeListEnd() {
+    _writeJsonArrayEnd();
+  }
+
+  void writeSetBegin(TSet set) {
+    _writeJsonArrayStart();
+    _writeJsonString(_Constants.getTypeNameBytesForTypeId(set.elementType));
+    _writeJsonInteger(set.length);
+  }
+
+  void writeSetEnd() {
+    _writeJsonArrayEnd();
+  }
+
+  void writeBool(bool b) {
+    if (b == null) b = false;
+    _writeJsonInteger(b ? 1 : 0);
+  }
+
+  void writeByte(int b) {
+    _writeJsonInteger(b);
+  }
+
+  void writeI16(int i16) {
+    _writeJsonInteger(i16);
+  }
+
+  void writeI32(int i32) {
+    _writeJsonInteger(i32);
+  }
+
+  void writeI64(int i64) {
+    _writeJsonInteger(i64);
+  }
+
+  void writeDouble(double d) {
+    _writeJsonDouble(d);
+  }
+
+  void writeString(String s) {
+    var bytes = s != null ? utf8Codec.encode(s) : new Uint8List.fromList([]);
+    _writeJsonString(bytes);
+  }
+
+  void writeBinary(Uint8List bytes) {
+    _writeJsonBase64(bytes);
+  }
+
+  /// read
+
+  Uint8List _readJsonString({bool skipContext: false}) {
+    List<int> bytes = [];
+
+    if (!skipContext) {
+      _context.read();
+    }
+
+    _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+    while (true) {
+      int byte = _reader.read();
+      if (byte == _Constants.QUOTE_BYTES[0]) {
+        break;
+      }
+
+      // escaped?
+      if (byte != _Constants.ESCSEQ_BYTES[0]) {
+        bytes.add(byte);
+        continue;
+      }
+
+      byte = _reader.read();
+
+      // distinguish between \u00XX and control chars like \n
+      if (byte != _Constants.ESCSEQ_BYTES[1]) {
+        String char = new String.fromCharCode(byte);
+        int offset = _Constants.ESCAPE_CHARS.indexOf(char);
+        if (offset == -1) {
+          throw new TProtocolError(
+              TProtocolErrorType.INVALID_DATA, "Expected control char");
+        }
+        byte = _Constants.ESCAPE_CHAR_VALS.codeUnitAt(offset);
+        bytes.add(byte);
+        continue;
+      }
+
+      // it's \u00XX
+      _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]);
+      _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]);
+      transport.readAll(_tempBuffer, 0, 2);
+      byte = _hexVal(_tempBuffer[0]) << 4 + _hexVal(_tempBuffer[1]);
+      bytes.add(byte);
+    }
+
+    return new Uint8List.fromList(bytes);
+  }
+
+  String _readJsonNumericChars() {
+    StringBuffer buffer = new StringBuffer();
+    while (true) {
+      if (!_Constants.isJsonNumeric(_reader.peek())) {
+        break;
+      }
+      buffer.write(new String.fromCharCode(_reader.read()));
+    }
+    return buffer.toString();
+  }
+
+  int _readJsonInteger() {
+    _context.read();
+
+    if (_context.escapeNumbers) {
+      _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+    }
+    String str = _readJsonNumericChars();
+    if (_context.escapeNumbers) {
+      _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+    }
+
+    try {
+      return int.parse(str);
+    } on FormatException catch (_) {
+      throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+          "Bad data encounted in numeric data");
+    }
+  }
+
+  double _readJsonDouble() {
+    _context.read();
+
+    if (_reader.peek() == _Constants.QUOTE_BYTES[0]) {
+      Uint8List bytes = _readJsonString(skipContext: true);
+      double d = double.parse(utf8Codec.decode(bytes), (_) {
+        throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+            "Bad data encounted in numeric data");
+      });
+      if (!_context.escapeNumbers && !d.isNaN && !d.isInfinite) {
+        throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+            "Numeric data unexpectedly quoted");
+      }
+      return d;
+    } else {
+      if (_context.escapeNumbers) {
+        // This will throw - we should have had a quote if escapeNumbers == true
+        _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+      }
+      return double.parse(_readJsonNumericChars(), (_) {
+        throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+            "Bad data encounted in numeric data");
+      });
+    }
+  }
+
+  Uint8List _readJsonBase64() {
+    // convert UTF-8 bytes of a Base 64 encoded string to binary bytes
+    Uint8List base64Bytes = _readJsonString();
+    String base64 = utf8Codec.decode(base64Bytes);
+
+    return new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64));
+  }
+
+  void _readJsonObjectStart() {
+    _context.read();
+    _readJsonSyntaxChar(_Constants.LBRACE_BYTES[0]);
+    _pushContext(new _PairContext(this));
+  }
+
+  void _readJsonObjectEnd() {
+    _readJsonSyntaxChar(_Constants.RBRACE_BYTES[0]);
+    _popContext();
+  }
+
+  void _readJsonArrayStart() {
+    _context.read();
+    _readJsonSyntaxChar(_Constants.LBRACKET_BYTES[0]);
+    _pushContext(new _ListContext(this));
+  }
+
+  void _readJsonArrayEnd() {
+    _readJsonSyntaxChar(_Constants.RBRACKET_BYTES[0]);
+    _popContext();
+  }
+
+  TMessage readMessageBegin() {
+    _resetContext();
+
+    _readJsonArrayStart();
+    if (_readJsonInteger() != VERSION_1) {
+      throw new TProtocolError(
+          TProtocolErrorType.BAD_VERSION, "Message contained bad version.");
+    }
+
+    Uint8List buffer = _readJsonString();
+    String name = utf8Codec.decode(buffer);
+    int type = _readJsonInteger();
+    int seqid = _readJsonInteger();
+
+    return new TMessage(name, type, seqid);
+  }
+
+  void readMessageEnd() {
+    _readJsonArrayEnd();
+  }
+
+  TStruct readStructBegin() {
+    _readJsonObjectStart();
+    return new TStruct();
+  }
+
+  void readStructEnd() {
+    _readJsonObjectEnd();
+  }
+
+  TField readFieldBegin() {
+    String name = "";
+    int type = TType.STOP;
+    int id = 0;
+
+    if (_reader.peek() != _Constants.RBRACE_BYTES[0]) {
+      id = _readJsonInteger();
+      _readJsonObjectStart();
+      type = _Constants.getTypeIdForTypeName(_readJsonString());
+    }
+
+    return new TField(name, type, id);
+  }
+
+  void readFieldEnd() {
+    _readJsonObjectEnd();
+  }
+
+  TMap readMapBegin() {
+    _readJsonArrayStart();
+    int keyType = _Constants.getTypeIdForTypeName(_readJsonString());
+    int valueType = _Constants.getTypeIdForTypeName(_readJsonString());
+    int length = _readJsonInteger();
+    _readJsonObjectStart();
+
+    return new TMap(keyType, valueType, length);
+  }
+
+  void readMapEnd() {
+    _readJsonObjectEnd();
+    _readJsonArrayEnd();
+  }
+
+  TList readListBegin() {
+    _readJsonArrayStart();
+    int elementType = _Constants.getTypeIdForTypeName(_readJsonString());
+    int length = _readJsonInteger();
+
+    return new TList(elementType, length);
+  }
+
+  void readListEnd() {
+    _readJsonArrayEnd();
+  }
+
+  TSet readSetBegin() {
+    _readJsonArrayStart();
+    int elementType = _Constants.getTypeIdForTypeName(_readJsonString());
+    int length = _readJsonInteger();
+
+    return new TSet(elementType, length);
+  }
+
+  void readSetEnd() {
+    _readJsonArrayEnd();
+  }
+
+  bool readBool() {
+    return _readJsonInteger() == 0 ? false : true;
+  }
+
+  int readByte() {
+    return _readJsonInteger();
+  }
+
+  int readI16() {
+    return _readJsonInteger();
+  }
+
+  int readI32() {
+    return _readJsonInteger();
+  }
+
+  int readI64() {
+    return _readJsonInteger();
+  }
+
+  double readDouble() {
+    return _readJsonDouble();
+  }
+
+  String readString() {
+    return utf8Codec.decode(_readJsonString());
+  }
+
+  Uint8List readBinary() {
+    return new Uint8List.fromList(_readJsonBase64());
+  }
+}
+
+class _Constants {
+  static const utf8codec = const Utf8Codec();
+
+  static final Uint8List HEX_0_BYTES = new Uint8List.fromList('0'.codeUnits);
+  static final Uint8List HEX_9_BYTES = new Uint8List.fromList('9'.codeUnits);
+  static final Uint8List HEX_A_BYTES = new Uint8List.fromList('a'.codeUnits);
+  static final Uint8List HEX_F_BYTES = new Uint8List.fromList('f'.codeUnits);
+  static final Uint8List COMMA_BYTES = new Uint8List.fromList(','.codeUnits);
+  static final Uint8List COLON_BYTES = new Uint8List.fromList(':'.codeUnits);
+  static final Uint8List LBRACE_BYTES = new Uint8List.fromList('{'.codeUnits);
+  static final Uint8List RBRACE_BYTES = new Uint8List.fromList('}'.codeUnits);
+  static final Uint8List LBRACKET_BYTES = new Uint8List.fromList('['.codeUnits);
+  static final Uint8List RBRACKET_BYTES = new Uint8List.fromList(']'.codeUnits);
+  static final Uint8List QUOTE_BYTES = new Uint8List.fromList('"'.codeUnits);
+  static final Uint8List BACKSLASH_BYTES =
+      new Uint8List.fromList(r'\'.codeUnits);
+
+  static final ESCSEQ_BYTES = new Uint8List.fromList(r'\u00'.codeUnits);
+
+  static final Uint8List JSON_CHAR_TABLE = new Uint8List.fromList([
+    0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+    'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, // 4 bytes
+    'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 4 bytes
+    0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+    0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+    1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes
+    1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes
+  ]);
+
+  static const String ESCAPE_CHARS = r'"\/bfnrt';
+  static const String ESCAPE_CHAR_VALS = '"\\/\b\f\n\r\t';
+
+  static const String NAME_BOOL = 'tf';
+  static const String NAME_BYTE = 'i8';
+  static const String NAME_I16 = 'i16';
+  static const String NAME_I32 = 'i32';
+  static const String NAME_I64 = 'i64';
+  static const String NAME_DOUBLE = 'dbl';
+  static const String NAME_STRUCT = 'rec';
+  static const String NAME_STRING = 'str';
+  static const String NAME_MAP = 'map';
+  static const String NAME_LIST = 'lst';
+  static const String NAME_SET = 'set';
+
+  static final Map<int, Uint8List> _TYPE_ID_TO_NAME_BYTES =
+      new Map.unmodifiable({
+    TType.BOOL: new Uint8List.fromList(NAME_BOOL.codeUnits),
+    TType.BYTE: new Uint8List.fromList(NAME_BYTE.codeUnits),
+    TType.I16: new Uint8List.fromList(NAME_I16.codeUnits),
+    TType.I32: new Uint8List.fromList(NAME_I32.codeUnits),
+    TType.I64: new Uint8List.fromList(NAME_I64.codeUnits),
+    TType.DOUBLE: new Uint8List.fromList(NAME_DOUBLE.codeUnits),
+    TType.STRING: new Uint8List.fromList(NAME_STRING.codeUnits),
+    TType.STRUCT: new Uint8List.fromList(NAME_STRUCT.codeUnits),
+    TType.MAP: new Uint8List.fromList(NAME_MAP.codeUnits),
+    TType.SET: new Uint8List.fromList(NAME_SET.codeUnits),
+    TType.LIST: new Uint8List.fromList(NAME_LIST.codeUnits)
+  });
+
+  static Uint8List getTypeNameBytesForTypeId(int typeId) {
+    if (!_TYPE_ID_TO_NAME_BYTES.containsKey(typeId)) {
+      throw new TProtocolError(
+          TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type");
+    }
+
+    return _TYPE_ID_TO_NAME_BYTES[typeId];
+  }
+
+  static final Map<String, int> _NAME_TO_TYPE_ID = new Map.unmodifiable({
+    NAME_BOOL: TType.BOOL,
+    NAME_BYTE: TType.BYTE,
+    NAME_I16: TType.I16,
+    NAME_I32: TType.I32,
+    NAME_I64: TType.I64,
+    NAME_DOUBLE: TType.DOUBLE,
+    NAME_STRING: TType.STRING,
+    NAME_STRUCT: TType.STRUCT,
+    NAME_MAP: TType.MAP,
+    NAME_SET: TType.SET,
+    NAME_LIST: TType.LIST
+  });
+
+  static int getTypeIdForTypeName(Uint8List bytes) {
+    String name = utf8codec.decode(bytes);
+    if (!_NAME_TO_TYPE_ID.containsKey(name)) {
+      throw new TProtocolError(
+          TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type");
+    }
+
+    return _NAME_TO_TYPE_ID[name];
+  }
+
+  static final Set<int> _JSON_NUMERICS = new Set.from([
+    '+'.codeUnitAt(0),
+    '-'.codeUnitAt(0),
+    '.'.codeUnitAt(0),
+    '0'.codeUnitAt(0),
+    '1'.codeUnitAt(0),
+    '2'.codeUnitAt(0),
+    '3'.codeUnitAt(0),
+    '4'.codeUnitAt(0),
+    '5'.codeUnitAt(0),
+    '6'.codeUnitAt(0),
+    '7'.codeUnitAt(0),
+    '8'.codeUnitAt(0),
+    '9'.codeUnitAt(0),
+    'E'.codeUnitAt(0),
+    'e'.codeUnitAt(0)
+  ]);
+
+  static bool isJsonNumeric(int byte) {
+    return _JSON_NUMERICS.contains(byte);
+  }
+}
+
+class _LookaheadReader {
+  final TJsonProtocol protocol;
+
+  _LookaheadReader(this.protocol);
+
+  bool _hasData = false;
+  final Uint8List _data = new Uint8List(1);
+
+  int read() {
+    if (_hasData) {
+      _hasData = false;
+    } else {
+      protocol.transport.readAll(_data, 0, 1);
+    }
+
+    return _data[0];
+  }
+
+  int peek() {
+    if (!_hasData) {
+      protocol.transport.readAll(_data, 0, 1);
+    }
+    _hasData = true;
+
+    return _data[0];
+  }
+}
+
+class _BaseContext {
+  final TJsonProtocol protocol;
+
+  _BaseContext(this.protocol);
+
+  void write() {}
+
+  void read() {}
+
+  bool get escapeNumbers => false;
+
+  String toString() => 'BaseContext';
+}
+
+class _ListContext extends _BaseContext {
+  _ListContext(TJsonProtocol protocol) : super(protocol);
+
+  bool _first = true;
+
+  void write() {
+    if (_first) {
+      _first = false;
+    } else {
+      protocol.transport.writeAll(_Constants.COMMA_BYTES);
+    }
+  }
+
+  void read() {
+    if (_first) {
+      _first = false;
+    } else {
+      protocol._readJsonSyntaxChar(_Constants.COMMA_BYTES[0]);
+    }
+  }
+
+  String toString() => 'ListContext';
+}
+
+class _PairContext extends _BaseContext {
+  _PairContext(TJsonProtocol protocol) : super(protocol);
+
+  bool _first = true;
+  bool _colon = true;
+
+  Uint8List get symbolBytes =>
+      _colon ? _Constants.COLON_BYTES : _Constants.COMMA_BYTES;
+
+  void write() {
+    if (_first) {
+      _first = false;
+      _colon = true;
+    } else {
+      protocol.transport.writeAll(symbolBytes);
+      _colon = !_colon;
+    }
+  }
+
+  void read() {
+    if (_first) {
+      _first = false;
+      _colon = true;
+    } else {
+      protocol._readJsonSyntaxChar(symbolBytes[0]);
+      _colon = !_colon;
+    }
+  }
+
+  bool get escapeNumbers => _colon;
+
+  String toString() => 'PairContext';
+}
diff --git a/lib/dart/lib/src/protocol/t_list.dart b/lib/dart/lib/src/protocol/t_list.dart
new file mode 100644
index 0000000..49f4673
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_list.dart
@@ -0,0 +1,25 @@
+/// 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.
+
+part of thrift;
+
+class TList {
+  final int elementType;
+  final int length;
+
+  TList(this.elementType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_map.dart b/lib/dart/lib/src/protocol/t_map.dart
new file mode 100644
index 0000000..efdf681
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_map.dart
@@ -0,0 +1,26 @@
+/// 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.
+
+part of thrift;
+
+class TMap {
+  final int keyType;
+  final int valueType;
+  final int length;
+
+  TMap(this.keyType, this.valueType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_message.dart b/lib/dart/lib/src/protocol/t_message.dart
new file mode 100644
index 0000000..cc7b886
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_message.dart
@@ -0,0 +1,35 @@
+/// 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.
+
+part of thrift;
+
+class TMessageType {
+  static const int CALL = 1;
+  static const int REPLY = 2;
+  static const int EXCEPTION = 3;
+  static const int ONEWAY = 4;
+}
+
+class TMessage {
+  final String name;
+  final int type;
+  final int seqid;
+
+  TMessage(this.name, this.type, this.seqid);
+
+  String toString() => "<TMessage name: '$name' type: $type seqid: $seqid>";
+}
diff --git a/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart
new file mode 100644
index 0000000..078a6d7
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart
@@ -0,0 +1,43 @@
+/// 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.
+
+part of thrift;
+
+/// Adapted from the C# version.
+class TMultiplexedProtocol extends TProtocolDecorator {
+  static const SEPARATOR = ':';
+
+  final String _serviceName;
+
+  TMultiplexedProtocol(TProtocol protocol, String serviceName)
+      : _serviceName = serviceName,
+        super(protocol) {
+    if (serviceName == null) {
+      throw new ArgumentError.notNull("serviceName");
+    }
+  }
+
+  void writeMessageBegin(TMessage message) {
+    if (message.type == TMessageType.CALL ||
+        message.type == TMessageType.ONEWAY) {
+      String name = _serviceName + SEPARATOR + message.name;
+      message = new TMessage(name, message.type, message.seqid);
+    }
+
+    super.writeMessageBegin(message);
+  }
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart
new file mode 100644
index 0000000..f49c032
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol.dart
@@ -0,0 +1,95 @@
+/// 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.
+
+part of thrift;
+
+abstract class TProtocol {
+  final TTransport transport;
+
+  TProtocol(this.transport);
+
+  /// Write
+  void writeMessageBegin(TMessage message);
+  void writeMessageEnd();
+
+  void writeStructBegin(TStruct struct);
+  void writeStructEnd();
+
+  void writeFieldBegin(TField field);
+  void writeFieldEnd();
+  void writeFieldStop();
+
+  void writeMapBegin(TMap map);
+  void writeMapEnd();
+
+  void writeListBegin(TList list);
+  void writeListEnd();
+
+  void writeSetBegin(TSet set);
+  void writeSetEnd();
+
+  void writeBool(bool b);
+
+  void writeByte(int b);
+
+  void writeI16(int i16);
+
+  void writeI32(int i32);
+
+  void writeI64(int i64);
+
+  void writeDouble(double d);
+
+  void writeString(String str);
+
+  void writeBinary(Uint8List bytes);
+
+  /// Read
+  TMessage readMessageBegin();
+  void readMessageEnd();
+
+  TStruct readStructBegin();
+  void readStructEnd();
+
+  TField readFieldBegin();
+  void readFieldEnd();
+
+  TMap readMapBegin();
+  void readMapEnd();
+
+  TList readListBegin();
+  void readListEnd();
+
+  TSet readSetBegin();
+  void readSetEnd();
+
+  bool readBool();
+
+  int readByte();
+
+  int readI16();
+
+  int readI32();
+
+  int readI64();
+
+  double readDouble();
+
+  String readString();
+
+  Uint8List readBinary();
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/lib/dart/lib/src/protocol/t_protocol_decorator.dart
new file mode 100644
index 0000000..9cd02f6
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_decorator.dart
@@ -0,0 +1,150 @@
+/// 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.
+
+part of thrift;
+
+/// Forward all operations to the wrapped protocol.  Used as a base class.
+///
+/// Adapted from the C# version.
+class TProtocolDecorator extends TProtocol {
+  final TProtocol _protocol;
+
+  TProtocolDecorator(TProtocol protocol)
+      : _protocol = protocol,
+        super(protocol.transport);
+
+  /// Write
+
+  void writeMessageBegin(TMessage message) {
+    _protocol.writeMessageBegin(message);
+  }
+
+  void writeMessageEnd() {
+    _protocol.writeMessageEnd();
+  }
+
+  void writeStructBegin(TStruct struct) {
+    _protocol.writeStructBegin(struct);
+  }
+
+  void writeStructEnd() {
+    _protocol.writeStructEnd();
+  }
+
+  void writeFieldBegin(TField field) {
+    _protocol.writeFieldBegin(field);
+  }
+
+  void writeFieldEnd() {
+    _protocol.writeFieldEnd();
+  }
+
+  void writeFieldStop() {
+    _protocol.writeFieldStop();
+  }
+
+  void writeMapBegin(TMap map) {
+    _protocol.writeMapBegin(map);
+  }
+
+  void writeMapEnd() {
+    _protocol.writeMapEnd();
+  }
+
+  void writeListBegin(TList list) {
+    _protocol.writeListBegin(list);
+  }
+
+  void writeListEnd() {
+    _protocol.writeListEnd();
+  }
+
+  void writeSetBegin(TSet set) {
+    _protocol.writeSetBegin(set);
+  }
+
+  void writeSetEnd() {
+    _protocol.writeSetEnd();
+  }
+
+  void writeBool(bool b) {
+    _protocol.writeBool(b);
+  }
+
+  void writeByte(int b) {
+    _protocol.writeByte(b);
+  }
+
+  void writeI16(int i16) {
+    _protocol.writeI16(i16);
+  }
+
+  void writeI32(int i32) {
+    _protocol.writeI32(i32);
+  }
+
+  void writeI64(int i64) {
+    _protocol.writeI64(i64);
+  }
+
+  void writeDouble(double d) {
+    _protocol.writeDouble(d);
+  }
+
+  void writeString(String str) {
+    _protocol.writeString(str);
+  }
+
+  void writeBinary(Uint8List bytes) {
+    _protocol.writeBinary(bytes);
+  }
+
+  /// Read
+  TMessage readMessageBegin() => _protocol.readMessageBegin();
+  void readMessageEnd() => _protocol.readMessageEnd();
+
+  TStruct readStructBegin() => _protocol.readStructBegin();
+  void readStructEnd() => _protocol.readStructEnd();
+
+  TField readFieldBegin() => _protocol.readFieldBegin();
+  void readFieldEnd() => _protocol.readFieldEnd();
+
+  TMap readMapBegin() => _protocol.readMapBegin();
+  void readMapEnd() => _protocol.readMapEnd();
+
+  TList readListBegin() => _protocol.readListBegin();
+  void readListEnd() => _protocol.readListEnd();
+
+  TSet readSetBegin() => _protocol.readSetBegin();
+  void readSetEnd() => _protocol.readSetEnd();
+
+  bool readBool() => _protocol.readBool();
+
+  int readByte() => _protocol.readByte();
+
+  int readI16() => _protocol.readI16();
+
+  int readI32() => _protocol.readI32();
+
+  int readI64() => _protocol.readI64();
+
+  double readDouble() => _protocol.readDouble();
+
+  String readString() => _protocol.readString();
+
+  Uint8List readBinary() => _protocol.readBinary();
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_error.dart b/lib/dart/lib/src/protocol/t_protocol_error.dart
new file mode 100644
index 0000000..456baeb
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_error.dart
@@ -0,0 +1,33 @@
+/// 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.
+
+part of thrift;
+
+class TProtocolErrorType {
+  static const int UNKNOWN = 0;
+  static const int INVALID_DATA = 1;
+  static const int NEGATIVE_SIZE = 2;
+  static const int SIZE_LIMIT = 3;
+  static const int BAD_VERSION = 4;
+  static const int NOT_IMPLEMENTED = 5;
+  static const int DEPTH_LIMIT = 6;
+}
+
+class TProtocolError extends TError {
+  TProtocolError([int type = TProtocolErrorType.UNKNOWN, String message = ""])
+      : super(type, message);
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_factory.dart b/lib/dart/lib/src/protocol/t_protocol_factory.dart
new file mode 100644
index 0000000..922c6cb
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_factory.dart
@@ -0,0 +1,22 @@
+/// 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.
+
+part of thrift;
+
+abstract class TProtocolFactory<T extends TProtocol> {
+  T getProtocol(TTransport transport);
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_util.dart b/lib/dart/lib/src/protocol/t_protocol_util.dart
new file mode 100644
index 0000000..ad20068
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_util.dart
@@ -0,0 +1,107 @@
+/// 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.
+
+part of thrift;
+
+class TProtocolUtil {
+  // equal to JavaScript Number.MAX_SAFE_INTEGER, 2^53 - 1
+  static const int defaultRecursionLimit = 9007199254740991;
+
+  static int maxRecursionLimit = defaultRecursionLimit;
+
+  static skip(TProtocol prot, int type) {
+    _skip(prot, type, maxRecursionLimit);
+  }
+
+  static _skip(TProtocol prot, int type, int recursionLimit) {
+    if (recursionLimit <= 0) {
+      throw new TProtocolError(
+          TProtocolErrorType.DEPTH_LIMIT, "Depth limit exceeded");
+    }
+
+    switch (type) {
+      case TType.BOOL:
+        prot.readBool();
+        break;
+
+      case TType.BYTE:
+        prot.readByte();
+        break;
+
+      case TType.I16:
+        prot.readI16();
+        break;
+
+      case TType.I32:
+        prot.readI32();
+        break;
+
+      case TType.I64:
+        prot.readI64();
+        break;
+
+      case TType.DOUBLE:
+        prot.readDouble();
+        break;
+
+      case TType.STRING:
+        prot.readBinary();
+        break;
+
+      case TType.STRUCT:
+        prot.readStructBegin();
+        while (true) {
+          TField field = prot.readFieldBegin();
+          if (field.type == TType.STOP) {
+            break;
+          }
+          _skip(prot, field.type, recursionLimit - 1);
+          prot.readFieldEnd();
+        }
+        prot.readStructEnd();
+        break;
+
+      case TType.MAP:
+        TMap map = prot.readMapBegin();
+        for (int i = 0; i < map.length; i++) {
+          _skip(prot, map.keyType, recursionLimit - 1);
+          _skip(prot, map.valueType, recursionLimit - 1);
+        }
+        prot.readMapEnd();
+        break;
+
+      case TType.SET:
+        TSet set = prot.readSetBegin();
+        for (int i = 0; i < set.length; i++) {
+          _skip(prot, set.elementType, recursionLimit - 1);
+        }
+        prot.readSetEnd();
+        break;
+
+      case TType.LIST:
+        TList list = prot.readListBegin();
+        for (int i = 0; i < list.length; i++) {
+          _skip(prot, list.elementType, recursionLimit - 1);
+        }
+        prot.readListEnd();
+        break;
+
+      default:
+        break;
+    }
+  }
+}
diff --git a/lib/dart/lib/src/protocol/t_set.dart b/lib/dart/lib/src/protocol/t_set.dart
new file mode 100644
index 0000000..b342537
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_set.dart
@@ -0,0 +1,25 @@
+/// 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.
+
+part of thrift;
+
+class TSet {
+  final int elementType;
+  final int length;
+
+  TSet(this.elementType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_struct.dart b/lib/dart/lib/src/protocol/t_struct.dart
new file mode 100644
index 0000000..0303f63
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_struct.dart
@@ -0,0 +1,24 @@
+/// 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.
+
+part of thrift;
+
+class TStruct {
+  final String name;
+
+  TStruct([this.name = ""]);
+}
diff --git a/lib/dart/lib/src/protocol/t_type.dart b/lib/dart/lib/src/protocol/t_type.dart
new file mode 100644
index 0000000..3919d96
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_type.dart
@@ -0,0 +1,34 @@
+/// 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.
+
+part of thrift;
+
+class TType {
+  static const int STOP = 0;
+  static const int VOID = 1;
+  static const int BOOL = 2;
+  static const int BYTE = 3;
+  static const int DOUBLE = 4;
+  static const int I16 = 6;
+  static const int I32 = 8;
+  static const int I64 = 10;
+  static const int STRING = 11;
+  static const int STRUCT = 12;
+  static const int MAP = 13;
+  static const int SET = 14;
+  static const int LIST = 15;
+}
diff --git a/lib/dart/lib/src/t_application_error.dart b/lib/dart/lib/src/t_application_error.dart
new file mode 100644
index 0000000..6f8abd4
--- /dev/null
+++ b/lib/dart/lib/src/t_application_error.dart
@@ -0,0 +1,104 @@
+/// 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.
+
+part of thrift;
+
+class TApplicationErrorType {
+  static const int UNKNOWN = 0;
+  static const int UNKNOWN_METHOD = 1;
+  static const int INVALID_MESSAGE_TYPE = 2;
+  static const int WRONG_METHOD_NAME = 3;
+  static const int BAD_SEQUENCE_ID = 4;
+  static const int MISSING_RESULT = 5;
+  static const int INTERNAL_ERROR = 6;
+  static const int PROTOCOL_ERROR = 7;
+  static const int INVALID_TRANSFORM = 8;
+  static const int INVALID_PROTOCOL = 9;
+  static const int UNSUPPORTED_CLIENT_TYPE = 10;
+}
+
+class TApplicationError extends TError {
+  static final TStruct _struct = new TStruct("TApplicationError");
+  static const int MESSAGE = 1;
+  static final TField _messageField =
+      new TField("message", TType.STRING, MESSAGE);
+  static const int TYPE = 2;
+  static final TField _typeField = new TField("type", TType.I32, TYPE);
+
+  TApplicationError(
+      [int type = TApplicationErrorType.UNKNOWN, String message = ""])
+      : super(type, message);
+
+  static TApplicationError read(TProtocol iprot) {
+    TField field;
+
+    String message = null;
+    int type = TApplicationErrorType.UNKNOWN;
+
+    iprot.readStructBegin();
+    while (true) {
+      field = iprot.readFieldBegin();
+
+      if (field.type == TType.STOP) {
+        break;
+      }
+
+      switch (field.id) {
+        case MESSAGE:
+          if (field.type == TType.STRING) {
+            message = iprot.readString();
+          } else {
+            TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+
+        case TYPE:
+          if (field.type == TType.I32) {
+            type = iprot.readI32();
+          } else {
+            TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+
+        default:
+          TProtocolUtil.skip(iprot, field.type);
+          break;
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    return new TApplicationError(type, message);
+  }
+
+  write(TProtocol oprot) {
+    oprot.writeStructBegin(_struct);
+
+    if (message != null && !message.isEmpty) {
+      oprot.writeFieldBegin(_messageField);
+      oprot.writeString(message);
+      oprot.writeFieldEnd();
+    }
+
+    oprot.writeFieldBegin(_typeField);
+    oprot.writeI32(type);
+    oprot.writeFieldEnd();
+
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+}
diff --git a/lib/dart/lib/src/t_base.dart b/lib/dart/lib/src/t_base.dart
new file mode 100644
index 0000000..d5571b6
--- /dev/null
+++ b/lib/dart/lib/src/t_base.dart
@@ -0,0 +1,37 @@
+/// 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.
+
+part of thrift;
+
+abstract class TBase {
+  /// Reads the TObject from the given input protocol.
+  void read(TProtocol iprot);
+
+  /// Writes the objects out to the [oprot] protocol.
+  void write(TProtocol oprot);
+
+  /// Check if a field is currently set or unset, using the [fieldId].
+  bool isSet(int fieldId);
+
+  /// Get a field's value by [fieldId]. Primitive types will be wrapped in the
+  /// appropriate "boxed" types.
+  getFieldValue(int fieldId);
+
+  /// Set a field's value by [fieldId]. Primitive types must be "boxed" in the
+  /// appropriate object wrapper type.
+  setFieldValue(int fieldId, Object value);
+}
diff --git a/lib/dart/lib/src/t_error.dart b/lib/dart/lib/src/t_error.dart
new file mode 100644
index 0000000..93ab732
--- /dev/null
+++ b/lib/dart/lib/src/t_error.dart
@@ -0,0 +1,27 @@
+/// 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.
+
+part of thrift;
+
+class TError extends Error {
+  final String message;
+  final int type;
+
+  TError(this.type, this.message);
+
+  String toString() => "<TError type: $type message: '$message'>";
+}
diff --git a/lib/dart/lib/src/t_processor.dart b/lib/dart/lib/src/t_processor.dart
new file mode 100644
index 0000000..dcf20fb
--- /dev/null
+++ b/lib/dart/lib/src/t_processor.dart
@@ -0,0 +1,24 @@
+/// 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.
+
+part of thrift;
+
+/// A processor is a generic object which operates upon an input stream and
+/// writes to some output stream.
+abstract class TProcessor {
+  bool process(TProtocol input, TProtocol output);
+}
diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart
new file mode 100644
index 0000000..1d44c62
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_buffered_transport.dart
@@ -0,0 +1,98 @@
+/// 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.
+
+part of thrift;
+
+/// Buffered implementation of [TTransport].
+class TBufferedTransport extends TTransport {
+  final List<int> _writeBuffer = [];
+  Iterator<int> _readIterator;
+
+  Uint8List _consumeWriteBuffer() {
+    Uint8List buffer = new Uint8List.fromList(_writeBuffer);
+    _writeBuffer.clear();
+    return buffer;
+  }
+
+  void _setReadBuffer(Uint8List readBuffer) {
+    _readIterator = readBuffer != null ? readBuffer.iterator : null;
+  }
+
+  void _reset({bool isOpen: false}) {
+    _isOpen = isOpen;
+    _writeBuffer.clear();
+    _readIterator = null;
+  }
+
+  bool get hasReadData => _readIterator != null;
+
+  bool _isOpen;
+  bool get isOpen => _isOpen;
+
+  Future open() async {
+    _reset(isOpen: true);
+  }
+
+  Future close() async {
+    _reset(isOpen: false);
+  }
+
+  int read(Uint8List buffer, int offset, int length) {
+    if (buffer == null) {
+      throw new ArgumentError.notNull("buffer");
+    }
+
+    if (offset + length > buffer.length) {
+      throw new ArgumentError("The range exceeds the buffer length");
+    }
+
+    if (_readIterator == null || length <= 0) {
+      return 0;
+    }
+
+    int i = 0;
+    while (i < length && _readIterator.moveNext()) {
+      buffer[offset + i] = _readIterator.current;
+      i++;
+    }
+
+    // cleanup iterator when we've reached the end
+    if (_readIterator.current == null) {
+      _readIterator = null;
+    }
+
+    return i;
+  }
+
+  void write(Uint8List buffer, int offset, int length) {
+    if (buffer == null) {
+      throw new ArgumentError.notNull("buffer");
+    }
+
+    if (offset + length > buffer.length) {
+      throw new ArgumentError("The range exceeds the buffer length");
+    }
+
+    _writeBuffer.addAll(buffer.sublist(offset, offset + length));
+  }
+
+  Future flush() {
+    _readIterator = _consumeWriteBuffer().iterator;
+
+    return new Future.value();
+  }
+}
diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart
new file mode 100644
index 0000000..baf8f32
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_framed_transport.dart
@@ -0,0 +1,83 @@
+/// 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.
+
+part of thrift;
+
+/// Framed [TTransport].
+///
+/// Adapted from the Java Framed transport.
+class TFramedTransport extends TBufferedTransport {
+  static const int headerByteCount = 4;
+
+  final TTransport _transport;
+
+  final Uint8List headerBytes = new Uint8List(headerByteCount);
+
+  TFramedTransport(TTransport transport) : _transport = transport {
+    if (transport == null) {
+      throw new ArgumentError.notNull("transport");
+    }
+  }
+
+  bool get isOpen => _transport.isOpen;
+
+  Future open() {
+    _reset(isOpen: true);
+    return _transport.open();
+  }
+
+  Future close() {
+    _reset(isOpen: false);
+    return _transport.close();
+  }
+
+  int read(Uint8List buffer, int offset, int length) {
+    if (hasReadData) {
+      int got = super.read(buffer, offset, length);
+      if (got > 0) return got;
+    }
+
+    _readFrame();
+
+    return super.read(buffer, offset, length);
+  }
+
+  void _readFrame() {
+    _transport.readAll(headerBytes, 0, headerByteCount);
+    int size = headerBytes.buffer.asByteData().getUint32(0);
+
+    if (size < 0) {
+      throw new TTransportError(
+          TTransportErrorType.UNKNOWN, "Read a negative frame size: $size");
+    }
+
+    Uint8List buffer = new Uint8List(size);
+    _transport.readAll(buffer, 0, size);
+    _setReadBuffer(buffer);
+  }
+
+  Future flush() {
+    Uint8List buffer = _consumeWriteBuffer();
+    int length = buffer.length;
+
+    headerBytes.buffer.asByteData().setUint32(0, length);
+    _transport.write(headerBytes, 0, headerByteCount);
+    _transport.write(buffer, 0, length);
+
+    return _transport.flush();
+  }
+}
diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart
new file mode 100644
index 0000000..0e99a1d
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_http_transport.dart
@@ -0,0 +1,100 @@
+/// 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.
+
+part of thrift;
+
+/// HTTP implementation of [TTransport].
+///
+/// For example:
+///
+///     var transport = new THttpClientTransport(new BrowserClient(),
+///         new THttpConfig(url, {'X-My-Custom-Header': 'my value'}));
+///     var protocol = new TJsonProtocol(transport);
+///     var client = new MyThriftServiceClient(protocol);
+///     var result = client.myMethod();
+///
+/// Adapted from the JS XHR HTTP transport.
+class THttpClientTransport extends TBufferedTransport {
+  final Client httpClient;
+  final THttpConfig config;
+
+  THttpClientTransport(this.httpClient, this.config) {
+    if (httpClient == null) {
+      throw new ArgumentError.notNull("httpClient");
+    }
+  }
+
+  Future close() async {
+    _reset(isOpen: false);
+    httpClient.close();
+  }
+
+  Future flush() {
+    var requestBody = CryptoUtils.bytesToBase64(_consumeWriteBuffer());
+
+    // Use a sync completer to ensure that the buffer can be read immediately
+    // after the read buffer is set, and avoid a race condition where another
+    // response could overwrite the read buffer.
+    var completer = new Completer.sync();
+
+    httpClient
+        .post(config.url, headers: config.headers, body: requestBody)
+        .then((response) {
+      Uint8List data;
+      try {
+        data = new Uint8List.fromList(
+            CryptoUtils.base64StringToBytes(response.body));
+      } on FormatException catch (_) {
+        throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+            "Expected a Base 64 encoded string.");
+      }
+
+      _setReadBuffer(data);
+      completer.complete();
+    });
+
+    return completer.future;
+  }
+}
+
+class THttpConfig {
+  final Uri url;
+
+  Map<String, String> _headers;
+  get headers => _headers;
+
+  THttpConfig(this.url, Map<String, String> headers) {
+    if (url == null || !url.hasAuthority) {
+      throw new ArgumentError("Invalid url");
+    }
+
+    _initHeaders(headers);
+  }
+
+  void _initHeaders(Map<String, String> initial) {
+    var h = {};
+
+    if (initial != null) {
+      h.addAll(initial);
+    }
+
+    h['Content-Type'] = 'application/x-thrift';
+    h['Accept'] = 'application/x-thrift';
+
+    _headers = new Map.unmodifiable(h);
+  }
+}
diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart
new file mode 100644
index 0000000..8ca0708
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_message_reader.dart
@@ -0,0 +1,99 @@
+/// 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.
+
+part of thrift;
+
+/// [TMessageReader] extracts a [TMessage] from bytes.  This is used to allow a
+/// transport to inspect the message seqid and map responses to requests.
+class TMessageReader {
+  final TProtocolFactory protocolFactory;
+
+  final int byteOffset;
+  final _TMessageReaderTransport _transport;
+
+  /// Construct a [MessageReader].  The optional [byteOffset] specifies the
+  /// number of bytes to skip before reading the [TMessage].
+  TMessageReader(this.protocolFactory, {int byteOffset: 0})
+      : _transport = new _TMessageReaderTransport(),
+        this.byteOffset = byteOffset;
+
+  TMessage readMessage(Uint8List bytes) {
+    _transport.reset(bytes, byteOffset);
+    TProtocol protocol = protocolFactory.getProtocol(_transport);
+    TMessage message = protocol.readMessageBegin();
+    _transport.reset(null);
+
+    return message;
+  }
+}
+
+/// An internal class used to support [TMessageReader].
+class _TMessageReaderTransport extends TTransport {
+  _TMessageReaderTransport();
+
+  Iterator<int> _readIterator;
+
+  void reset(Uint8List bytes, [int offset = 0]) {
+    if (bytes == null) {
+      _readIterator = null;
+      return;
+    }
+
+    if (offset > bytes.length) {
+      throw new ArgumentError("The offset exceeds the bytes length");
+    }
+
+    _readIterator = bytes.iterator;
+
+    for (var i = 0; i < offset; i++) {
+      _readIterator.moveNext();
+    }
+  }
+
+  get isOpen => true;
+
+  Future open() => throw new UnsupportedError("Unsupported in MessageReader");
+
+  Future close() => throw new UnsupportedError("Unsupported in MessageReader");
+
+  int read(Uint8List buffer, int offset, int length) {
+    if (buffer == null) {
+      throw new ArgumentError.notNull("buffer");
+    }
+
+    if (offset + length > buffer.length) {
+      throw new ArgumentError("The range exceeds the buffer length");
+    }
+
+    if (_readIterator == null || length <= 0) {
+      return 0;
+    }
+
+    int i = 0;
+    while (i < length && _readIterator.moveNext()) {
+      buffer[offset + i] = _readIterator.current;
+      i++;
+    }
+
+    return i;
+  }
+
+  void write(Uint8List buffer, int offset, int length) =>
+      throw new UnsupportedError("Unsupported in MessageReader");
+
+  Future flush() => throw new UnsupportedError("Unsupported in MessageReader");
+}
diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart
new file mode 100644
index 0000000..74618b6
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket.dart
@@ -0,0 +1,38 @@
+/// 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.
+
+part of thrift;
+
+enum TSocketState { CLOSED, OPEN }
+
+abstract class TSocket {
+  Stream<TSocketState> get onState;
+
+  Stream<String> get onError;
+
+  Stream<Uint8List> get onMessage;
+
+  bool get isOpen;
+
+  bool get isClosed;
+
+  Future open();
+
+  Future close();
+
+  void send(Uint8List data);
+}
diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart
new file mode 100644
index 0000000..ad7e48e
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket_transport.dart
@@ -0,0 +1,175 @@
+/// 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.
+
+part of thrift;
+
+/// Socket implementation of [TTransport].
+///
+/// For example:
+///
+///     var transport = new TClientSocketTransport(new TWebSocket(url));
+///     var protocol = new TBinaryProtocol(transport);
+///     var client = new MyThriftServiceClient(protocol);
+///     var result = client.myMethod();
+///
+/// Adapted from the JS WebSocket transport.
+abstract class TSocketTransport extends TBufferedTransport {
+  final Logger log = new Logger('thrift.TSocketTransport');
+
+  final TSocket socket;
+
+  /// A transport using the provided [socket].
+  TSocketTransport(this.socket) {
+    if (socket == null) {
+      throw new ArgumentError.notNull('socket');
+    }
+
+    socket.onError.listen((String e) => log.warning(e));
+    socket.onMessage.listen(handleIncomingMessage);
+  }
+
+  bool get isOpen => socket.isOpen;
+
+  Future open() {
+    _reset(isOpen: true);
+    return socket.open();
+  }
+
+  Future close() {
+    _reset(isOpen: false);
+    return socket.close();
+  }
+
+  /// Make an incoming message available to read from the transport.
+  void handleIncomingMessage(Uint8List messageBytes) {
+    _setReadBuffer(messageBytes);
+  }
+}
+
+/// [TClientSocketTransport] is a basic client socket transport.  It sends
+/// outgoing messages and expects a response.
+///
+/// NOTE: This transport expects a single threaded server, as it will process
+/// responses in FIFO order.
+class TClientSocketTransport extends TSocketTransport {
+  final List<Completer<Uint8List>> _completers = [];
+
+  TClientSocketTransport(TSocket socket) : super(socket);
+
+  Future flush() {
+    Uint8List bytes = _consumeWriteBuffer();
+
+    // Use a sync completer to ensure that the buffer can be read immediately
+    // after the read buffer is set, and avoid a race condition where another
+    // response could overwrite the read buffer.
+    Completer completer = new Completer.sync();
+    _completers.add(completer);
+
+    socket.send(bytes);
+
+    return completer.future;
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    if (_completers.isNotEmpty) {
+      var completer = _completers.removeAt(0);
+      completer.complete();
+    }
+  }
+}
+
+/// [TAsyncClientSocketTransport] sends outgoing messages and expects an
+/// asynchronous response.
+///
+/// NOTE: This transport uses a [MessageReader] to read a [TMessage] when an
+/// incoming message arrives to correlate a response to a request, using the
+/// seqid.
+class TAsyncClientSocketTransport extends TSocketTransport {
+  static const defaultTimeout = const Duration(seconds: 30);
+
+  final Map<int, Completer<Uint8List>> _completers = {};
+
+  final TMessageReader messageReader;
+
+  final Duration responseTimeout;
+
+  TAsyncClientSocketTransport(TSocket socket, TMessageReader messageReader,
+      {Duration responseTimeout: defaultTimeout})
+      : this.messageReader = messageReader,
+        this.responseTimeout = responseTimeout,
+        super(socket);
+
+  Future flush() {
+    Uint8List bytes = _consumeWriteBuffer();
+    TMessage message = messageReader.readMessage(bytes);
+    int seqid = message.seqid;
+
+    // Use a sync completer to ensure that the buffer can be read immediately
+    // after the read buffer is set, and avoid a race condition where another
+    // response could overwrite the read buffer.
+    Completer completer = new Completer.sync();
+    _completers[seqid] = completer;
+
+    if (responseTimeout != null) {
+      new Future.delayed(responseTimeout, () {
+        var completer = _completers.remove(seqid);
+        if (completer != null) {
+          completer.completeError(
+              new TimeoutException("Response timed out.", responseTimeout));
+        }
+      });
+    }
+
+    socket.send(bytes);
+
+    return completer.future;
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    TMessage message = messageReader.readMessage(messageBytes);
+    var completer = _completers.remove(message.seqid);
+    if (completer != null) {
+      completer.complete();
+    }
+  }
+}
+
+/// [TServerSocketTransport] listens for incoming messages.  When it sends a
+/// response, it does not expect an acknowledgement.
+class TServerSocketTransport extends TSocketTransport {
+  final StreamController _onIncomingMessageController;
+  Stream get onIncomingMessage => _onIncomingMessageController.stream;
+
+  TServerSocketTransport(TSocket socket)
+      : _onIncomingMessageController = new StreamController.broadcast(),
+        super(socket);
+
+  Future flush() async {
+    Uint8List message = _consumeWriteBuffer();
+    socket.send(message);
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    _onIncomingMessageController.add(null);
+  }
+}
diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart
new file mode 100644
index 0000000..563d5eb
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport.dart
@@ -0,0 +1,70 @@
+/// 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.
+
+part of thrift;
+
+abstract class TTransport {
+  /// Queries whether the transport is open.
+  /// Returns [true] if the transport is open.
+  bool get isOpen;
+
+  /// Opens the transport for reading/writing.
+  /// Throws [TTransportError] if the transport could not be opened.
+  Future open();
+
+  /// Closes the transport.
+  Future close();
+
+  /// Reads up to [length] bytes into [buffer], starting at [offset].
+  /// Returns the number of bytes actually read.
+  /// Throws [TTransportError] if there was an error reading data
+  int read(Uint8List buffer, int offset, int length);
+
+  /// Guarantees that all of [length] bytes are actually read off the transport.
+  /// Returns the number of bytes actually read, which must be equal to
+  /// [length].
+  /// Throws [TTransportError] if there was an error reading data
+  int readAll(Uint8List buffer, int offset, int length) {
+    int got = 0;
+    int ret = 0;
+    while (got < length) {
+      ret = read(buffer, offset + got, length - got);
+      if (ret <= 0) {
+        throw new TTransportError(
+            TTransportErrorType.UNKNOWN,
+            "Cannot read. Remote side has closed. Tried to read $length "
+            "bytes, but only got $got bytes.");
+      }
+      got += ret;
+    }
+    return got;
+  }
+
+  /// Writes up to [len] bytes from the buffer.
+  /// Throws [TTransportError] if there was an error writing data
+  void write(Uint8List buffer, int offset, int length);
+
+  /// Writes the [bytes] to the output.
+  /// Throws [TTransportError] if there was an error writing data
+  void writeAll(Uint8List buffer) {
+    write(buffer, 0, buffer.length);
+  }
+
+  /// Flush any pending data out of a transport buffer.
+  /// Throws [TTransportError] if there was an error writing out data.
+  Future flush();
+}
diff --git a/lib/dart/lib/src/transport/t_transport_error.dart b/lib/dart/lib/src/transport/t_transport_error.dart
new file mode 100644
index 0000000..d3508e0
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_error.dart
@@ -0,0 +1,31 @@
+/// 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.
+
+part of thrift;
+
+class TTransportErrorType {
+  static const int UNKNOWN = 0;
+  static const int NOT_OPEN = 1;
+  static const int ALREADY_OPEN = 2;
+  static const int TIMED_OUT = 3;
+  static const int END_OF_FILE = 4;
+}
+
+class TTransportError extends TError {
+  TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""])
+      : super(type, message);
+}
diff --git a/lib/dart/lib/src/transport/t_transport_factory.dart b/lib/dart/lib/src/transport/t_transport_factory.dart
new file mode 100644
index 0000000..7a10461
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_factory.dart
@@ -0,0 +1,27 @@
+/// 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.
+
+part of thrift;
+
+/// Factory class used to create wrapped instance of a [TTransport]. This is
+/// used primarily in servers.
+///
+/// Adapted from the Java version.
+class TTransportFactory {
+  Future<TTransport> getTransport(TTransport transport) =>
+      new Future.value(transport);
+}
diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart
new file mode 100644
index 0000000..2483726
--- /dev/null
+++ b/lib/dart/lib/thrift.dart
@@ -0,0 +1,58 @@
+/// 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.
+
+library thrift;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show ByteData;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show Client;
+import 'package:logging/logging.dart';
+
+part 'src/t_application_error.dart';
+part 'src/t_base.dart';
+part 'src/t_error.dart';
+part 'src/t_processor.dart';
+
+part 'src/protocol/t_binary_protocol.dart';
+part 'src/protocol/t_field.dart';
+part 'src/protocol/t_json_protocol.dart';
+part 'src/protocol/t_list.dart';
+part 'src/protocol/t_map.dart';
+part 'src/protocol/t_message.dart';
+part 'src/protocol/t_multiplexed_protocol.dart';
+part 'src/protocol/t_protocol.dart';
+part 'src/protocol/t_protocol_decorator.dart';
+part 'src/protocol/t_protocol_error.dart';
+part 'src/protocol/t_protocol_factory.dart';
+part 'src/protocol/t_protocol_util.dart';
+part 'src/protocol/t_set.dart';
+part 'src/protocol/t_struct.dart';
+part 'src/protocol/t_type.dart';
+
+part 'src/transport/t_buffered_transport.dart';
+part 'src/transport/t_framed_transport.dart';
+part 'src/transport/t_http_transport.dart';
+part 'src/transport/t_message_reader.dart';
+part 'src/transport/t_socket.dart';
+part 'src/transport/t_transport.dart';
+part 'src/transport/t_transport_error.dart';
+part 'src/transport/t_transport_factory.dart';
+part 'src/transport/t_socket_transport.dart';
diff --git a/lib/dart/lib/thrift_browser.dart b/lib/dart/lib/thrift_browser.dart
new file mode 100644
index 0000000..2ebc257
--- /dev/null
+++ b/lib/dart/lib/thrift_browser.dart
@@ -0,0 +1,22 @@
+/// 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.
+
+library thrift_browser;
+
+/// Classes that are only supported in browser applications go here
+
+export 'src/browser/t_web_socket.dart' show TWebSocket;
diff --git a/lib/dart/lib/thrift_console.dart b/lib/dart/lib/thrift_console.dart
new file mode 100644
index 0000000..48a83d1
--- /dev/null
+++ b/lib/dart/lib/thrift_console.dart
@@ -0,0 +1,23 @@
+/// 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.
+
+library thrift_console;
+
+/// Classes that are only supported in console applications go here
+
+export 'src/console/t_tcp_socket.dart' show TTcpSocket;
+export 'src/console/t_web_socket.dart' show TWebSocket;
diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml
new file mode 100644
index 0000000..f64d980
--- /dev/null
+++ b/lib/dart/pubspec.yaml
@@ -0,0 +1,36 @@
+# 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.
+
+name: thrift
+version: 1.0.0-dev
+description: >
+  A Dart library for Apache Thrift
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+documentation: https://github.com/apache/thrift
+environment:
+  sdk: ">=1.12.0 <2.0.0"
+dependencies:
+  crypto: "^0.9.0"
+  http: "^0.11.3"
+  logging: "^0.11.0"
+dev_dependencies:
+  coverage: "^0.7.2"
+  dart_dev: "^1.0.1"
+  dart_style: "^0.2.0"
+  mockito: "^0.11.0"
+  test: "^0.12.0"
diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart
new file mode 100644
index 0000000..88ddd4f
--- /dev/null
+++ b/lib/dart/test/protocol/t_protocol_test.dart
@@ -0,0 +1,374 @@
+// 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.
+
+library thrift.test.transport.t_json_protocol_test;
+
+import 'dart:async';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  final message = new TMessage('my message', TMessageType.ONEWAY, 123);
+
+  TProtocol protocol;
+
+  Primitive getPrimitive(int tType) {
+    switch (tType) {
+      case TType.BOOL:
+        return new Primitive(protocol.readBool, protocol.writeBool, false);
+
+      case TType.BYTE:
+        return new Primitive(protocol.readByte, protocol.writeByte, 0);
+
+      case TType.I16:
+        return new Primitive(protocol.readI16, protocol.writeI16, 0);
+
+      case TType.I32:
+        return new Primitive(protocol.readI32, protocol.writeI32, 0);
+
+      case TType.I64:
+        return new Primitive(protocol.readI64, protocol.writeI64, 0);
+
+      case TType.DOUBLE:
+        return new Primitive(protocol.readDouble, protocol.writeDouble, 0);
+
+      case TType.STRING:
+        return new Primitive(protocol.readString, protocol.writeString, '');
+
+      default:
+        throw new UnsupportedError("Unsupported TType $tType");
+    }
+  }
+
+  Future primitiveTest(Primitive primitive, input) async {
+    primitive.write(input);
+    protocol.writeMessageEnd();
+
+    await protocol.transport.flush();
+
+    protocol.readMessageBegin();
+    var output = primitive.read();
+
+    expect(output, input);
+  }
+
+  Future primitiveNullTest(Primitive primitive) async {
+    primitive.write(null);
+    protocol.writeMessageEnd();
+
+    await protocol.transport.flush();
+
+    protocol.readMessageBegin();
+    var output = primitive.read();
+
+    expect(output, primitive.defaultValue);
+  }
+
+  var sharedTests = () {
+    test('Test message', () async {
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      var subject = protocol.readMessageBegin();
+
+      expect(subject.name, message.name);
+      expect(subject.type, message.type);
+      expect(subject.seqid, message.seqid);
+    });
+
+    test('Test struct', () async {
+      var input = new TStruct();
+
+      protocol.writeStructBegin(input);
+      protocol.writeStructEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readStructBegin();
+
+      // name is not serialized, see C# version for reference
+      expect(output, isNotNull);
+    });
+
+    test('Test field', () async {
+      var input = new TField('my field', TType.MAP, 123);
+
+      protocol.writeFieldBegin(input);
+      protocol.writeFieldEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readFieldBegin();
+
+      // name is not serialized, see C# version for reference
+      expect(output.type, input.type);
+      expect(output.id, input.id);
+    });
+
+    test('Test map', () async {
+      var input = new TMap(TType.STRING, TType.STRUCT, 123);
+
+      protocol.writeMapBegin(input);
+      protocol.writeMapEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readMapBegin();
+
+      expect(output.keyType, input.keyType);
+      expect(output.valueType, input.valueType);
+      expect(output.length, input.length);
+    });
+
+    test('Test list', () async {
+      var input = new TList(TType.STRING, 123);
+
+      protocol.writeListBegin(input);
+      protocol.writeListEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readListBegin();
+
+      expect(output.elementType, input.elementType);
+      expect(output.length, input.length);
+    });
+
+    test('Test set', () async {
+      var input = new TSet(TType.STRING, 123);
+
+      protocol.writeSetBegin(input);
+      protocol.writeSetEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readListBegin();
+
+      expect(output.elementType, input.elementType);
+      expect(output.length, input.length);
+    });
+
+    test('Test bool', () async {
+      await primitiveTest(getPrimitive(TType.BOOL), true);
+    });
+
+    test('Test bool null', () async {
+      await primitiveNullTest(getPrimitive(TType.BOOL));
+    });
+
+    test('Test byte', () async {
+      await primitiveTest(getPrimitive(TType.BYTE), 64);
+    });
+
+    test('Test byte null', () async {
+      await primitiveNullTest(getPrimitive(TType.BYTE));
+    });
+
+    test('Test I16', () async {
+      await primitiveTest(getPrimitive(TType.I16), 32767);
+    });
+
+    test('Test I16 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I16));
+    });
+
+    test('Test I32', () async {
+      await primitiveTest(getPrimitive(TType.I32), 2147483647);
+    });
+
+    test('Test I32 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I32));
+    });
+
+    test('Test I64', () async {
+      await primitiveTest(getPrimitive(TType.I64), 9223372036854775807);
+    });
+
+    test('Test I64 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I64));
+    });
+
+    test('Test double', () async {
+      await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926);
+    });
+
+    test('Test double null', () async {
+      await primitiveNullTest(getPrimitive(TType.DOUBLE));
+    });
+
+    test('Test string', () async {
+      var input = 'There are only two hard things in computer science: '
+          'cache invalidation, naming things, and off-by-one errors.';
+      await primitiveTest(getPrimitive(TType.STRING), input);
+    });
+
+    test('Test string null', () async {
+      await primitiveNullTest(getPrimitive(TType.STRING));
+    });
+
+    test('Test binary', () async {
+      var input = new Uint8List.fromList(new List.filled(100, 123));
+
+      protocol.writeBinary(input);
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readBinary();
+
+      expect(output.length, input.length);
+      expect(output.every((i) => i == 123), isTrue);
+    });
+
+    test('Test complex struct', () async {
+      // {1: {10: 20}, 2: {30: 40}}
+      protocol.writeStructBegin(new TStruct());
+      protocol.writeFieldBegin(new TField('success', TType.MAP, 0));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.MAP, 2));
+
+      protocol.writeI32(1); // key
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(10); // key
+      protocol.writeI32(20); // value
+      protocol.writeMapEnd();
+
+      protocol.writeI32(2); // key
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(30); // key
+      protocol.writeI32(40); // value
+      protocol.writeMapEnd();
+
+      protocol.writeMapEnd();
+      protocol.writeFieldEnd();
+      protocol.writeFieldStop();
+      protocol.writeStructEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      protocol.readStructBegin();
+      expect(protocol.readFieldBegin().type, TType.MAP);
+      expect(protocol.readMapBegin().length, 2);
+
+      expect(protocol.readI32(), 1); // key
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 10); // key
+      expect(protocol.readI32(), 20); // value
+      protocol.readMapEnd();
+
+      expect(protocol.readI32(), 2); // key
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 30); // key
+      expect(protocol.readI32(), 40); // value
+      protocol.readMapEnd();
+
+      protocol.readMapEnd();
+      protocol.readFieldEnd();
+      protocol.readStructEnd();
+      protocol.readMessageEnd();
+    });
+
+    test('Test nested maps and lists', () async {
+      // {1: [{10: 20}], 2: [{30: 40}]}
+      protocol.writeMapBegin(new TMap(TType.I32, TType.LIST, 2));
+
+      protocol.writeI32(1); // key
+      protocol.writeListBegin(new TList(TType.MAP, 1));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(10); // key
+      protocol.writeI32(20); // value
+      protocol.writeMapEnd();
+      protocol.writeListEnd();
+
+      protocol.writeI32(2); // key
+      protocol.writeListBegin(new TList(TType.MAP, 1));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(30); // key
+      protocol.writeI32(40); // value
+      protocol.writeMapEnd();
+      protocol.writeListEnd();
+
+      protocol.writeMapEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      expect(protocol.readMapBegin().length, 2);
+
+      expect(protocol.readI32(), 1); // key
+      expect(protocol.readListBegin().length, 1);
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 10); // key
+      expect(protocol.readI32(), 20); // value
+      protocol.readMapEnd();
+      protocol.readListEnd();
+
+      expect(protocol.readI32(), 2); // key
+      expect(protocol.readListBegin().length, 1);
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 30); // key
+      expect(protocol.readI32(), 40); // value
+      protocol.readMapEnd();
+      protocol.readListEnd();
+
+      protocol.readMapEnd();
+      protocol.readMessageEnd();
+    });
+  };
+
+  group('JSON', () {
+    setUp(() {
+      protocol = new TJsonProtocol(new TBufferedTransport());
+      protocol.writeMessageBegin(message);
+    });
+
+    group('shared tests', sharedTests);
+  });
+
+  group('binary', () {
+    setUp(() {
+      protocol = new TBinaryProtocol(new TBufferedTransport());
+      protocol.writeMessageBegin(message);
+    });
+
+    group('shared tests', sharedTests);
+  });
+}
+
+class Primitive {
+  final Function read;
+  final Function write;
+  final defaultValue;
+
+  Primitive(this.read, this.write, this.defaultValue);
+}
diff --git a/lib/dart/test/t_application_error_test.dart b/lib/dart/test/t_application_error_test.dart
new file mode 100644
index 0000000..511d8d6
--- /dev/null
+++ b/lib/dart/test/t_application_error_test.dart
@@ -0,0 +1,46 @@
+// 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.
+
+library thrift.test.t_application_error_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  TProtocol protocol;
+
+  setUp(() {
+    protocol = new TBinaryProtocol(new TBufferedTransport());
+  });
+
+  test('Write and read an application error', () {
+    var expectedType = TApplicationErrorType.INTERNAL_ERROR;
+    var expectedMessage = 'test error message';
+
+    TApplicationError error =
+        new TApplicationError(expectedType, expectedMessage);
+    error.write(protocol);
+
+    protocol.transport.flush();
+
+    TApplicationError subject = TApplicationError.read(protocol);
+
+    expect(subject, isNotNull);
+    expect(subject.type, expectedType);
+    expect(subject.message, expectedMessage);
+  });
+}
diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart
new file mode 100644
index 0000000..7fcab3e
--- /dev/null
+++ b/lib/dart/test/transport/t_http_transport_test.dart
@@ -0,0 +1,165 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Encoding;
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show BaseRequest;
+import 'package:http/http.dart' show Client;
+import 'package:http/http.dart' show Response;
+import 'package:http/http.dart' show StreamedResponse;
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  const utf8Codec = const Utf8Codec();
+
+  group('THttpClientTransport', () {
+    FakeHttpClient client;
+    THttpClientTransport transport;
+
+    setUp(() {
+      client = new FakeHttpClient(sync: false);
+      var config = new THttpConfig(Uri.parse('http://localhost'), {});
+      transport = new THttpClientTransport(client, config);
+    });
+
+    test('Test transport sends body', () async {
+      var expectedText = 'my request';
+      transport.writeAll(utf8Codec.encode(expectedText));
+
+      expect(client.postRequest, isEmpty);
+
+      await transport.flush();
+
+      expect(client.postRequest, isNotEmpty);
+
+      var requestText =
+          utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest));
+      expect(requestText, expectedText);
+    });
+
+    test('Test transport receives response', () async {
+      var expectedText = 'my response';
+      var expectedBytes = utf8Codec.encode(expectedText);
+      client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+      transport.writeAll(utf8Codec.encode('my request'));
+      expect(transport.hasReadData, isFalse);
+
+      await transport.flush();
+
+      expect(transport.hasReadData, isTrue);
+
+      var buffer = new Uint8List(expectedBytes.length);
+      transport.readAll(buffer, 0, expectedBytes.length);
+
+      var bufferText = utf8Codec.decode(buffer);
+      expect(bufferText, expectedText);
+    });
+  });
+
+  group('THttpClientTransport with multiple messages', () {
+    FakeHttpClient client;
+    THttpClientTransport transport;
+
+    setUp(() {
+      client = new FakeHttpClient(sync: true);
+      var config = new THttpConfig(Uri.parse('http://localhost'), {});
+      transport = new THttpClientTransport(client, config);
+    });
+
+    test('Test read correct buffer after flush', () async {
+      String bufferText;
+      var expectedText = 'response 1';
+      var expectedBytes = utf8Codec.encode(expectedText);
+
+      // prepare a response
+      transport.writeAll(utf8Codec.encode('request 1'));
+      client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(expectedBytes.length);
+        transport.readAll(buffer, 0, expectedBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // prepare a second response
+      transport.writeAll(utf8Codec.encode('request 2'));
+      var response2Bytes = utf8Codec.encode('response 2');
+      client.postResponse = CryptoUtils.bytesToBase64(response2Bytes);
+      await transport.flush();
+
+      await responseReady;
+      expect(bufferText, expectedText);
+    });
+  });
+}
+
+class FakeHttpClient implements Client {
+  String postResponse = '';
+  String postRequest = '';
+
+  final bool sync;
+
+  FakeHttpClient({this.sync: false});
+
+  Future<Response> post(url,
+      {Map<String, String> headers, body, Encoding encoding}) {
+    postRequest = body;
+    var response = new Response(postResponse, 200);
+
+    if (sync) {
+      return new Future.sync(() => response);
+    } else {
+      return new Future.value(response);
+    }
+  }
+
+  Future<Response> head(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Response> get(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Response> put(url,
+          {Map<String, String> headers, body, Encoding encoding}) =>
+      throw new UnimplementedError();
+
+  Future<Response> patch(url,
+          {Map<String, String> headers, body, Encoding encoding}) =>
+      throw new UnimplementedError();
+
+  Future<Response> delete(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<String> read(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<StreamedResponse> send(BaseRequest request) =>
+      throw new UnimplementedError();
+
+  void close() => throw new UnimplementedError();
+}
diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart
new file mode 100644
index 0000000..997df0d
--- /dev/null
+++ b/lib/dart/test/transport/t_socket_transport_test.dart
@@ -0,0 +1,312 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  const utf8Codec = const Utf8Codec();
+
+  final requestText = 'my test request';
+  final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText));
+  final requestBase64 = CryptoUtils.bytesToBase64(requestBytes);
+
+  final responseText = 'response 1';
+  final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText));
+  final responseBase64 = CryptoUtils.bytesToBase64(responseBytes);
+
+  final framedResponseBase64 =
+      CryptoUtils.bytesToBase64(_getFramedResponse(responseBytes));
+
+  group('TClientSocketTransport', () {
+    FakeSocket socket;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: false);
+      await socket.open();
+      transport = new TClientSocketTransport(socket);
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test client sending data over transport', () async {
+      expect(socket.sendPayload, isNull);
+
+      Future responseReady = transport.flush();
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(socket.sendPayload, isNotNull);
+      expect(socket.sendPayload, requestBytes);
+
+      // simulate a response
+      socket.receiveFakeMessage(responseBase64);
+
+      await responseReady;
+      var buffer = new Uint8List(responseBytes.length);
+      transport.readAll(buffer, 0, responseBytes.length);
+      var bufferText = utf8Codec.decode(buffer);
+
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TClientSocketTransport with FramedTransport', () {
+    FakeSocket socket;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      transport = new TFramedTransport(new TClientSocketTransport(socket));
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test client sending data over framed transport', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      socket.receiveFakeMessage(framedResponseBase64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TAsyncClientSocketTransport', () {
+    FakeSocket socket;
+    FakeProtocolFactory protocolFactory;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      protocolFactory = new FakeProtocolFactory();
+      protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+      transport = new TAsyncClientSocketTransport(
+          socket, new TMessageReader(protocolFactory),
+          responseTimeout: Duration.ZERO);
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test response correlates to correct request', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+      socket.receiveFakeMessage(responseBase64);
+
+      // simulate a second response
+      var response2Text = 'response 2';
+      var response2Bytes =
+          new Uint8List.fromList(utf8Codec.encode(response2Text));
+      var response2Base64 = CryptoUtils.bytesToBase64(response2Bytes);
+      protocolFactory.message = new TMessage('foo2', TMessageType.REPLY, 124);
+      socket.receiveFakeMessage(response2Base64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+
+    test('Test response timeout', () async {
+      Future responseReady = transport.flush();
+      expect(responseReady, throwsA(new isInstanceOf<TimeoutException>()));
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TAsyncClientSocketTransport with TFramedTransport', () {
+    FakeSocket socket;
+    FakeProtocolFactory protocolFactory;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      protocolFactory = new FakeProtocolFactory();
+      protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+      var messageReader = new TMessageReader(protocolFactory,
+          byteOffset: TFramedTransport.headerByteCount);
+
+      transport = new TFramedTransport(new TAsyncClientSocketTransport(
+          socket, messageReader,
+          responseTimeout: Duration.ZERO));
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test async client sending data over framed transport', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+      socket.receiveFakeMessage(framedResponseBase64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TServerTransport', () {
+    test('Test server transport listens to socket', () async {
+      var socket = new FakeSocket();
+      await socket.open();
+      expect(socket.isOpen, isTrue);
+
+      var transport = new TServerSocketTransport(socket);
+      expect(transport.hasReadData, isFalse);
+
+      socket.receiveFakeMessage(requestBase64);
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(transport.hasReadData, isTrue);
+
+      var buffer = new Uint8List(requestBytes.length);
+      transport.readAll(buffer, 0, requestBytes.length);
+
+      var bufferText = utf8Codec.decode(buffer);
+      expect(bufferText, requestText);
+    });
+
+    test('Test server sending data over transport', () async {
+      var socket = new FakeSocket();
+      await socket.open();
+
+      var transport = new TServerSocketTransport(socket);
+
+      transport.writeAll(responseBytes);
+      expect(socket.sendPayload, isNull);
+
+      transport.flush();
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(socket.sendPayload, isNotNull);
+      expect(socket.sendPayload, responseBytes);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+}
+
+class FakeSocket extends TSocket {
+  final StreamController<TSocketState> _onStateController;
+  Stream<TSocketState> get onState => _onStateController.stream;
+
+  final StreamController<Object> _onErrorController;
+  Stream<Object> get onError => _onErrorController.stream;
+
+  final StreamController<Uint8List> _onMessageController;
+  Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+  FakeSocket({bool sync: false})
+      : _onStateController = new StreamController.broadcast(sync: sync),
+        _onErrorController = new StreamController.broadcast(sync: sync),
+        _onMessageController = new StreamController.broadcast(sync: sync);
+
+  bool _isOpen;
+
+  bool get isOpen => _isOpen;
+
+  bool get isClosed => !isOpen;
+
+  Future open() async {
+    _isOpen = true;
+    _onStateController.add(TSocketState.OPEN);
+  }
+
+  Future close() async {
+    _isOpen = false;
+    _onStateController.add(TSocketState.CLOSED);
+  }
+
+  Uint8List _sendPayload;
+  Uint8List get sendPayload => _sendPayload;
+
+  void send(Uint8List data) {
+    if (!isOpen) throw new StateError('The socket is not open');
+
+    _sendPayload = data;
+  }
+
+  void receiveFakeMessage(String base64) {
+    if (!isOpen) throw new StateError('The socket is not open');
+
+    var message =
+        new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64));
+    _onMessageController.add(message);
+  }
+}
+
+class FakeProtocolFactory implements TProtocolFactory {
+  FakeProtocolFactory();
+
+  TMessage message;
+
+  getProtocol(TTransport transport) => new FakeProtocol(message);
+}
+
+class FakeProtocol extends Mock implements TProtocol {
+  FakeProtocol(this._message);
+
+  TMessage _message;
+
+  readMessageBegin() => _message;
+}
+
+Uint8List _getFramedResponse(Uint8List responseBytes) {
+  var byteOffset = TFramedTransport.headerByteCount;
+  var response = new Uint8List(byteOffset + responseBytes.length);
+
+  response.buffer.asByteData().setInt32(0, responseBytes.length);
+  response.setAll(byteOffset, responseBytes);
+
+  return response;
+}
diff --git a/lib/dart/test/transport/t_transport_test.dart b/lib/dart/test/transport/t_transport_test.dart
new file mode 100644
index 0000000..0bb381a
--- /dev/null
+++ b/lib/dart/test/transport/t_transport_test.dart
@@ -0,0 +1,41 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+/// Common transport tests
+void main() {
+  group('TTransportFactory', () {
+    test('transport is returned from base factory', () async {
+      TTransport result;
+      TTransport transport = null;
+
+      var factory = new TTransportFactory();
+
+      result = await factory.getTransport(transport);
+      expect(result, isNull);
+
+      transport = new TBufferedTransport();
+      result = await factory.getTransport(transport);
+
+      expect(result, transport);
+    });
+  });
+}
diff --git a/lib/dart/tool/dev.dart b/lib/dart/tool/dev.dart
new file mode 100644
index 0000000..27f8b8f
--- /dev/null
+++ b/lib/dart/tool/dev.dart
@@ -0,0 +1,33 @@
+// 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.
+
+library tool.dev;
+
+import 'package:dart_dev/dart_dev.dart' show dev, config;
+
+main(List<String> args) async {
+  // https://github.com/Workiva/dart_dev
+
+  var directories = ['lib/', 'test/', 'tool/'];
+  config.analyze.entryPoints = directories;
+  config.format.directories = directories;
+  config.copyLicense
+    ..licensePath = 'LICENSE_HEADER'
+    ..directories = directories;
+
+  await dev(args);
+}